Commit 8fedd40b10e4d2e387246fdfd0a9fa37717124e2

Authored by 权海
1 parent 4ba6cb4d

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

... ... @@ -15,6 +15,7 @@ import '../../../../core/services/raw_data_service/health_raw_models.dart';
import '../../../../core/services/raw_data_service/platform_ios/apple_health_raw_local_notification.dart';
import '../../../../core/services/raw_data_service/platform_ohos/ohos_harmony_health_raw_data_sync_service_factory.dart';
import '../../../../core/services/raw_data_service/platform_ohos/ohos_health_raw_data_sync_service.dart';
import '../../../../core/services/raw_data_service/platform_ohos/ohos_sqlite_write_benchmark.dart';
import '../../../../data/local/user_preferences_storage.dart';
import '../../../../l10n/l10n_extensions.dart';
import '../../../../core/platform/pigeon_api_facade.dart';
... ... @@ -38,6 +39,7 @@ enum DeveloperOptionsAction {
shareHealthPipelineRecord,
sendTestHrvLocalNotification,
pullOhosRawData,
runOhosSqliteWriteBenchmark,
clearHealthRawDataDatabaseAndUploadLog,
globalEnv,
}
... ... @@ -95,6 +97,10 @@ class DeveloperOptionsController extends GetxController {
action: DeveloperOptionsAction.pullOhosRawData,
),
DeveloperOptionsItem(
title: '测试 OHOS SQLite 写入性能',
action: DeveloperOptionsAction.runOhosSqliteWriteBenchmark,
),
DeveloperOptionsItem(
title: '清除本地数据库、上传日志',
action: DeveloperOptionsAction.clearHealthRawDataDatabaseAndUploadLog,
),
... ... @@ -122,6 +128,10 @@ class DeveloperOptionsController extends GetxController {
action: DeveloperOptionsAction.pullOhosRawData,
),
DeveloperOptionsItem(
title: '测试 OHOS SQLite 写入性能',
action: DeveloperOptionsAction.runOhosSqliteWriteBenchmark,
),
DeveloperOptionsItem(
title: '清除本地数据库、上传日志',
action: DeveloperOptionsAction.clearHealthRawDataDatabaseAndUploadLog,
),
... ... @@ -152,6 +162,9 @@ class DeveloperOptionsController extends GetxController {
case DeveloperOptionsAction.pullOhosRawData:
await pullOhosRawData();
break;
case DeveloperOptionsAction.runOhosSqliteWriteBenchmark:
await runOhosSqliteWriteBenchmark();
break;
case DeveloperOptionsAction.clearHealthRawDataDatabaseAndUploadLog:
await clearHealthRawDataDatabaseAndUploadLog();
break;
... ... @@ -357,6 +370,20 @@ class DeveloperOptionsController extends GetxController {
}
}
Future<void> runOhosSqliteWriteBenchmark() async {
try {
late final String report;
await LoadingService.instance.run(() async {
report = await const OhosSqliteWriteBenchmark().run();
});
await _platformHostApi.shareText(report);
Get.snackbar('OHOS SQLite 性能测试完成', report);
} catch (error, stackTrace) {
AppLogger.e('Run OHOS SQLite write benchmark failed', error, stackTrace);
Get.snackbar('OHOS SQLite 性能测试失败', error.toString());
}
}
Future<void> clearHealthRawDataDatabaseAndUploadLog() async {
try {
await _healthRawDataCoreService.clearLocalDatabaseAndUploadLog();
... ...
... ... @@ -22,15 +22,17 @@ import '../health_sleep_calculator.dart';
import '../platform_ios/apple_health_raw_data_core_service.dart';
import '../platform_ios/apple_health_raw_local_notification.dart';
import 'huawei_health_raw_stress_calculator.dart';
import 'ohos_health_raw_data_events.dart';
import 'ohos_health_raw_data_sync_service.dart';
import 'ohos_health_raw_result_upload_service.dart';
class OHOSHealthRawDataCoreService {
static const int defaultLookbackDays = 183;
static const int defaultLookbackMonths = 2;
static const int defaultReadChunkDays = 7;
static const String _calculateLogMarker = '[OHOS_HEALTH_CALCULATE]';
static const String _dailyStressLogMarker = '[OHOS_DAILY_STRESS_CALC]';
static const String _sleepCalcLogMarker = '[OHOS_SLEEP_CALC]';
static const String _profileLogMarker = '[OHOS_HEALTH_RAW_PROFILE]';
OHOSHealthRawDataCoreService({
HealthRawDataSource? rawDataSource,
... ... @@ -225,24 +227,37 @@ class OHOSHealthRawDataCoreService {
required int readChunkDays,
required int? forceStartTime,
}) async {
final totalStopwatch = Stopwatch()..start();
_profileLog(
'core_start forceStartTime=${forceStartTime ?? ''} '
'endTime=${endTime ?? ''} readChunkDays=$readChunkDays',
);
if (readChunkDays <= 0) {
throw ArgumentError.value(readChunkDays, 'readChunkDays');
}
final userId = _userId;
final ensureStopwatch = Stopwatch()..start();
await _localStore.ensureReadable(userId);
_profileLog(
'core_ensureReadable_finish userId=$userId '
'elapsedMs=${ensureStopwatch.elapsedMilliseconds}',
);
final effectiveEndTime =
endTime ?? DateTime.now().millisecondsSinceEpoch ~/ 1000;
final earliestStartTime = DateTime.now()
.subtract(const Duration(days: defaultLookbackDays))
.millisecondsSinceEpoch ~/
1000;
final earliestStartTime = _twoMonthLookbackStart(effectiveEndTime);
final requestedStartTime =
math.max(forceStartTime ?? earliestStartTime, earliestStartTime);
if (effectiveEndTime < requestedStartTime) {
throw ArgumentError.value(endTime, 'endTime');
}
final authStopwatch = Stopwatch()..start();
final hasAuthorization = await _hasHealthReadAuthorizationSafely();
_profileLog(
'core_authorization_finish userId=$userId '
'hasAuthorization=$hasAuthorization '
'elapsedMs=${authStopwatch.elapsedMilliseconds}',
);
final willSyncRawData =
hasAuthorization && _rawDataSource is OhosHealthRawDataSource;
final syncStartTime = willSyncRawData ? DateTime.now() : null;
... ... @@ -260,23 +275,70 @@ class OHOSHealthRawDataCoreService {
'endTime=$effectiveEndTime syncedRawStartTime=$syncedRawStartTime '
'syncResults=$syncResults',
);
_profileLog(
'core_sync_finish userId=$userId willSyncRawData=$willSyncRawData '
'resultCount=${syncResults.length} storedCount='
'${syncResults.fold<int>(0, (sum, result) => sum + result.storedCount)} '
'elapsedMs=${syncElapsed.inMilliseconds}',
);
final calculationStartTime = DateTime.now();
final storedResult = await _calculateAndStoreSafely(
_publishCalculationEvent(
type: OhosHealthRawDataPipelineEventType.calculationStarted,
flow: 'calculateAndStore',
userId: userId,
requestedStartTime: requestedStartTime,
effectiveEndTime: effectiveEndTime,
earliestStartTime: earliestStartTime,
syncedRawStartTime: syncedRawStartTime,
readChunkDays: readChunkDays,
startTime: requestedStartTime,
endTime: effectiveEndTime,
);
late final _OhosStoredCalculationResult storedResult;
try {
storedResult = await _calculateAndStoreSafely(
userId: userId,
requestedStartTime: requestedStartTime,
effectiveEndTime: effectiveEndTime,
earliestStartTime: earliestStartTime,
syncedRawStartTime: syncedRawStartTime,
readChunkDays: readChunkDays,
);
} catch (error) {
final calculationElapsed =
DateTime.now().difference(calculationStartTime);
_publishCalculationEvent(
type: OhosHealthRawDataPipelineEventType.calculationFailed,
flow: 'calculateAndStore',
userId: userId,
startTime: requestedStartTime,
endTime: effectiveEndTime,
elapsedMs: calculationElapsed.inMilliseconds,
error: error.toString(),
);
rethrow;
}
final calculationElapsed = DateTime.now().difference(calculationStartTime);
_profileLog(
'core_calculateAndStore_finish userId=$userId '
'hrv=${storedResult.result.hrvStressPoints.length} '
'realtime=${storedResult.result.realtimeStressPoints.length} '
'daily=${storedResult.result.dailyStressPoints.length} '
'sleep=${storedResult.result.sleepResults.length} '
'elapsedMs=${calculationElapsed.inMilliseconds}',
);
final uploadScheduleStopwatch = Stopwatch()..start();
_scheduleResultUpload();
_profileLog(
'core_uploadSchedule_finish userId=$userId '
'elapsedMs=${uploadScheduleStopwatch.elapsedMilliseconds}',
);
final notificationStopwatch = Stopwatch()..start();
await _sendLocalNotificationsAfterCalculationSafely(
result: storedResult.result,
hasExistingHrv: storedResult.hasExistingHrv,
hasExistingSleep: storedResult.hasExistingSleep,
);
_profileLog(
'core_localNotification_finish userId=$userId '
'elapsedMs=${notificationStopwatch.elapsedMilliseconds}',
);
if (_hasStoredRawData(syncResults) ||
_hasCalculatedResult(storedResult.result)) {
_showDebugTimingToast(
... ... @@ -284,6 +346,23 @@ class OHOSHealthRawDataCoreService {
calculationElapsed: calculationElapsed,
);
}
_profileLog(
'core_finish userId=$userId syncElapsedMs=${syncElapsed.inMilliseconds} '
'calculationElapsedMs=${calculationElapsed.inMilliseconds} '
'totalElapsedMs=${totalStopwatch.elapsedMilliseconds}',
);
_publishCalculationEvent(
type: OhosHealthRawDataPipelineEventType.calculationSucceeded,
flow: 'calculateAndStore',
userId: userId,
startTime: requestedStartTime,
endTime: effectiveEndTime,
elapsedMs: calculationElapsed.inMilliseconds,
hrvCount: storedResult.result.hrvStressPoints.length,
realtimeCount: storedResult.result.realtimeStressPoints.length,
dailyCount: storedResult.result.dailyStressPoints.length,
sleepCount: storedResult.result.sleepResults.length,
);
return storedResult.result;
}
... ... @@ -295,9 +374,11 @@ class OHOSHealthRawDataCoreService {
required int? syncedRawStartTime,
required int readChunkDays,
}) async {
final totalStopwatch = Stopwatch()..start();
var hasExistingHrv = false;
var hasExistingSleep = false;
try {
final contextStopwatch = Stopwatch()..start();
final hrvContextStart =
await _localStore.latestHrvSourceStartTime(userId);
final realtimeContextStart =
... ... @@ -317,6 +398,10 @@ class OHOSHealthRawDataCoreService {
final latestRawSleepDataTime = await _latestOhosRawDataTime(
OhosHealthRawDataType.sleepAnalysis,
);
_profileLog(
'calculate_contextQuery_finish userId=$userId '
'elapsedMs=${contextStopwatch.elapsedMilliseconds}',
);
hasExistingHrv = latestHrvRawEndTime != null;
hasExistingSleep = latestSleepResultTime != null;
... ... @@ -400,34 +485,73 @@ class OHOSHealthRawDataCoreService {
'realtimeRecomputeStartTime=$realtimeRecomputeStartTime',
);
final hrvFetchStopwatch = Stopwatch()..start();
final hrvPoints = await _fetchRawDataInChunks(
HealthDataUploadType.hrv.type,
hrvStartTime,
effectiveEndTime,
readChunkDays: readChunkDays,
);
_profileLog(
'calculate_fetchRaw_finish userId=$userId '
'name=hrv dataType=${HealthDataUploadType.hrv.type} '
'startTime=$hrvStartTime endTime=$effectiveEndTime '
'count=${hrvPoints.length} '
'elapsedMs=${hrvFetchStopwatch.elapsedMilliseconds}',
);
final heartRateFetchStopwatch = Stopwatch()..start();
final heartRatePoints = await _fetchRawDataInChunks(
HealthDataUploadType.heartRate.type,
heartRateStartTime,
effectiveEndTime,
readChunkDays: readChunkDays,
);
_profileLog(
'calculate_fetchRaw_finish userId=$userId '
'name=heartRate dataType=${HealthDataUploadType.heartRate.type} '
'startTime=$heartRateStartTime endTime=$effectiveEndTime '
'count=${heartRatePoints.length} '
'elapsedMs=${heartRateFetchStopwatch.elapsedMilliseconds}',
);
final restingHeartRateFetchStopwatch = Stopwatch()..start();
final restingHeartRatePoints = await _fetchRawDataInChunks(
HealthDataUploadType.restingHeartRate.type,
heartRateStartTime,
effectiveEndTime,
readChunkDays: readChunkDays,
);
_profileLog(
'calculate_fetchRaw_finish userId=$userId '
'name=restingHeartRate '
'dataType=${HealthDataUploadType.restingHeartRate.type} '
'startTime=$heartRateStartTime endTime=$effectiveEndTime '
'count=${restingHeartRatePoints.length} '
'elapsedMs=${restingHeartRateFetchStopwatch.elapsedMilliseconds}',
);
final sleepFetchStopwatch = Stopwatch()..start();
final sleepIntervals = await _fetchSleepIntervalsInChunks(
sleepStartTime,
effectiveEndTime,
readChunkDays: readChunkDays,
);
_profileLog(
'calculate_fetchRaw_finish userId=$userId name=sleep '
'startTime=$sleepStartTime endTime=$effectiveEndTime '
'count=${sleepIntervals.length} '
'elapsedMs=${sleepFetchStopwatch.elapsedMilliseconds}',
);
final workoutFetchStopwatch = Stopwatch()..start();
final workoutIntervals = await _fetchWorkoutIntervalsInChunks(
heartRateStartTime,
effectiveEndTime,
readChunkDays: readChunkDays,
);
_profileLog(
'calculate_fetchRaw_finish userId=$userId name=workout '
'startTime=$heartRateStartTime endTime=$effectiveEndTime '
'count=${workoutIntervals.length} '
'elapsedMs=${workoutFetchStopwatch.elapsedMilliseconds}',
);
final latestRawHrvTime = _latestRawDataTime(hrvPoints);
final latestRawHrTime = _latestRawDataTime(heartRatePoints);
final latestRawSleepTime = _latestRawDataTime(sleepIntervals);
... ... @@ -445,6 +569,7 @@ class OHOSHealthRawDataCoreService {
'needSleep=${_needsNewResult(latestRawSleepTime, latestSleepResultTime)}',
);
final isolateStopwatch = Stopwatch()..start();
final result = await Isolate.run(
() => HuaweiHealthRawStressCalculator(userId: userId).calculate(
hrvPoints: hrvPoints,
... ... @@ -457,12 +582,23 @@ class OHOSHealthRawDataCoreService {
),
debugName: 'OHOSHealthRawStressCalculator',
);
_profileLog(
'calculate_isolate_finish userId=$userId '
'hrvInput=${hrvPoints.length} heartRateInput=${heartRatePoints.length} '
'restingHeartRateInput=${restingHeartRatePoints.length} '
'sleepInput=${sleepIntervals.length} workoutInput=${workoutIntervals.length} '
'hrv=${result.hrvStressPoints.length} '
'realtime=${result.realtimeStressPoints.length} '
'daily=${result.dailyStressPoints.length} '
'elapsedMs=${isolateStopwatch.elapsedMilliseconds}',
);
_logInfo(
'$_calculateLogMarker result_counts userId=$userId '
'hrv=${result.hrvStressPoints.length} '
'realtime=${result.realtimeStressPoints.length} '
'daily=${result.dailyStressPoints.length}',
);
final filterStopwatch = Stopwatch()..start();
final newResult = result.copyWith(
hrvStressPoints: _filterNewHrvStressPoints(
result.hrvStressPoints,
... ... @@ -475,6 +611,14 @@ class OHOSHealthRawDataCoreService {
recomputeStartTime: realtimeRecomputeStartTime,
),
);
_profileLog(
'calculate_filterNew_finish userId=$userId '
'inputHrv=${result.hrvStressPoints.length} '
'inputRealtime=${result.realtimeStressPoints.length} '
'newHrv=${newResult.hrvStressPoints.length} '
'newRealtime=${newResult.realtimeStressPoints.length} '
'elapsedMs=${filterStopwatch.elapsedMilliseconds}',
);
_logInfo(
'$_calculateLogMarker new_result_counts userId=$userId '
'latestHrvRawEndTime=$latestHrvRawEndTime '
... ... @@ -482,27 +626,52 @@ class OHOSHealthRawDataCoreService {
'hrv=${newResult.hrvStressPoints.length} '
'realtime=${newResult.realtimeStressPoints.length}',
);
final resultStoreStopwatch = Stopwatch()..start();
await _localStore.upsertResult(newResult);
_profileLog(
'calculate_storeResult_finish userId=$userId '
'hrv=${newResult.hrvStressPoints.length} '
'realtime=${newResult.realtimeStressPoints.length} '
'elapsedMs=${resultStoreStopwatch.elapsedMilliseconds}',
);
_logInfo(
'$_calculateLogMarker result_stored userId=$userId '
'hrv=${newResult.hrvStressPoints.length} '
'realtime=${newResult.realtimeStressPoints.length}',
);
final dailyStopwatch = Stopwatch()..start();
final dailyStressPoints = await _calculateAndStoreDailyStressPoints(
userId: userId,
realtimePoints: newResult.realtimeStressPoints,
nowSeconds: effectiveEndTime,
);
_profileLog(
'calculate_daily_finish userId=$userId '
'count=${dailyStressPoints.length} '
'elapsedMs=${dailyStopwatch.elapsedMilliseconds}',
);
final sleepStopwatch = Stopwatch()..start();
final sleepResults = await _calculateAndStoreSleepResults(
userId: userId,
sleepIntervals: sleepIntervals,
latestSleepResultTime: latestSleepResultTime,
);
_profileLog(
'calculate_sleep_finish userId=$userId count=${sleepResults.length} '
'elapsedMs=${sleepStopwatch.elapsedMilliseconds}',
);
_logInfo(
'calculate_daily_sleep_stored userId=$userId '
'daily=${dailyStressPoints.length} sleep=${sleepResults.length}',
);
_profileLog(
'calculate_finish userId=$userId '
'hrv=${newResult.hrvStressPoints.length} '
'realtime=${newResult.realtimeStressPoints.length} '
'daily=${dailyStressPoints.length} sleep=${sleepResults.length} '
'elapsedMs=${totalStopwatch.elapsedMilliseconds}',
);
return _OhosStoredCalculationResult(
result: newResult.copyWith(
dailyStressPoints: dailyStressPoints,
... ... @@ -512,6 +681,10 @@ class OHOSHealthRawDataCoreService {
hasExistingSleep: hasExistingSleep,
);
} catch (error, stackTrace) {
_profileLog(
'calculate_failed userId=$userId '
'elapsedMs=${totalStopwatch.elapsedMilliseconds} error=$error',
);
_logError(
'$_calculateLogMarker calculate_failed userId=$userId',
error,
... ... @@ -531,10 +704,26 @@ class OHOSHealthRawDataCoreService {
var cursor = startTime;
while (cursor <= endTime) {
final chunkEnd = math.min(cursor + chunkSeconds - 1, endTime);
final points = await _rawDataSource.getRawData(
dataType,
cursor,
chunkEnd,
final chunkStopwatch = Stopwatch()..start();
final List<HealthKitRawDataPoint> points;
try {
points = await _rawDataSource.getRawData(
dataType,
cursor,
chunkEnd,
);
} catch (error) {
_profileLog(
'rawChunk_failed dataType=$dataType startTime=$cursor '
'endTime=$chunkEnd elapsedMs=${chunkStopwatch.elapsedMilliseconds} '
'error=$error',
);
rethrow;
}
_profileLog(
'rawChunk_finish dataType=$dataType startTime=$cursor '
'endTime=$chunkEnd count=${points.length} '
'elapsedMs=${chunkStopwatch.elapsedMilliseconds}',
);
for (final point in points) {
yield point;
... ... @@ -552,12 +741,27 @@ class OHOSHealthRawDataCoreService {
var cursor = startTime;
while (cursor <= endTime) {
final chunkEnd = math.min(cursor + chunkSeconds - 1, endTime);
final groups = await _rawDataSource.getRawSleepData(cursor, chunkEnd);
final chunkStopwatch = Stopwatch()..start();
final List<HealthKitRawSleepDataPoint> groups;
try {
groups = await _rawDataSource.getRawSleepData(cursor, chunkEnd);
} catch (error) {
_profileLog(
'sleepChunk_failed startTime=$cursor endTime=$chunkEnd '
'elapsedMs=${chunkStopwatch.elapsedMilliseconds} error=$error',
);
rethrow;
}
final points = groups
.expand((group) => group.sleepDataPoints)
.where(
(point) => point.endTime >= cursor && point.startTime <= chunkEnd)
.toList();
_profileLog(
'sleepChunk_finish startTime=$cursor endTime=$chunkEnd '
'groupCount=${groups.length} count=${points.length} '
'elapsedMs=${chunkStopwatch.elapsedMilliseconds}',
);
for (final point in points) {
yield point;
}
... ... @@ -574,9 +778,27 @@ class OHOSHealthRawDataCoreService {
var cursor = startTime;
while (cursor <= endTime) {
final chunkEnd = math.min(cursor + chunkSeconds - 1, endTime);
final points = await _rawDataSource.getRawWorkoutData(cursor, chunkEnd);
for (final point in points.where(
(point) => point.endTime >= cursor && point.startTime <= chunkEnd)) {
final chunkStopwatch = Stopwatch()..start();
final List<HealthKitRawWorkoutDataPoint> points;
try {
points = await _rawDataSource.getRawWorkoutData(cursor, chunkEnd);
} catch (error) {
_profileLog(
'workoutChunk_failed startTime=$cursor endTime=$chunkEnd '
'elapsedMs=${chunkStopwatch.elapsedMilliseconds} error=$error',
);
rethrow;
}
final filteredPoints = points
.where(
(point) => point.endTime >= cursor && point.startTime <= chunkEnd)
.toList(growable: false);
_profileLog(
'workoutChunk_finish startTime=$cursor endTime=$chunkEnd '
'count=${filteredPoints.length} rawCount=${points.length} '
'elapsedMs=${chunkStopwatch.elapsedMilliseconds}',
);
for (final point in filteredPoints) {
yield point;
}
cursor = chunkEnd + 1;
... ... @@ -1022,13 +1244,7 @@ class OHOSHealthRawDataCoreService {
int? _rawBackfillStartTime(int? latestDataTime) {
if (latestDataTime == null) return null;
final backfillTime = math.max(
0,
latestDataTime -
OhosHealthRawDataSyncService.incrementalBackfillDays *
Duration.secondsPerDay,
);
return _localDay(backfillTime).millisecondsSinceEpoch ~/ 1000;
return _localDay(latestDataTime).millisecondsSinceEpoch ~/ 1000;
}
int? _boundedNullableStartTime(int? startTime, int earliestStartTime) {
... ... @@ -1108,6 +1324,7 @@ class OHOSHealthRawDataCoreService {
final dailyStressPoints = <HealthRawDailyStressPoint>[];
final emptyDates = <int>[];
for (final date in affectedDates) {
final dayStopwatch = Stopwatch()..start();
if (date != todayDate && existingDates.contains(date)) {
_logInfo(
'$_dailyStressLogMarker refresh_existing userId=$userId date=$date',
... ... @@ -1119,6 +1336,7 @@ class OHOSHealthRawDataCoreService {
startTime: startTime,
endTime: endTime,
);
final queryElapsedMs = dayStopwatch.elapsedMilliseconds;
_logInfo(
'$_dailyStressLogMarker day_query userId=$userId date=$date '
'startTime=$startTime endTime=$endTime '
... ... @@ -1133,18 +1351,31 @@ class OHOSHealthRawDataCoreService {
).firstOrNull;
if (point == null) {
emptyDates.add(date);
_profileLog(
'dailyStress_day_finish userId=$userId date=$date '
'dayRealtime=${dayRealtimePoints.length} hasResult=false '
'queryElapsedMs=$queryElapsedMs '
'elapsedMs=${dayStopwatch.elapsedMilliseconds}',
);
_logInfo(
'$_dailyStressLogMarker no_result userId=$userId date=$date',
);
continue;
}
dailyStressPoints.add(point);
_profileLog(
'dailyStress_day_finish userId=$userId date=$date '
'dayRealtime=${dayRealtimePoints.length} hasResult=true '
'queryElapsedMs=$queryElapsedMs '
'elapsedMs=${dayStopwatch.elapsedMilliseconds}',
);
_logInfo(
'$_dailyStressLogMarker result userId=$userId date=$date '
'stressValue=${point.stressValue} stressScore=${point.stressScore} '
'state=${point.state.value}',
);
}
final dailyStoreStopwatch = Stopwatch()..start();
await _localStore.upsertDailyStressPoints(
userId: userId,
points: dailyStressPoints,
... ... @@ -1153,6 +1384,11 @@ class OHOSHealthRawDataCoreService {
userId: userId,
dates: emptyDates,
);
_profileLog(
'dailyStress_store_finish userId=$userId '
'stored=${dailyStressPoints.length} deletedEmpty=${emptyDates.length} '
'elapsedMs=${dailyStoreStopwatch.elapsedMilliseconds}',
);
_logInfo(
'$_dailyStressLogMarker stored userId=$userId '
'stored=${dailyStressPoints.length} deletedEmptyDates=$emptyDates',
... ... @@ -1184,6 +1420,7 @@ class OHOSHealthRawDataCoreService {
);
final results = <HealthRawSleepResult>[];
for (final day in days) {
final dayStopwatch = Stopwatch()..start();
final calculation = HealthSleepCalculator.calculateDay(
day: day,
sleepIntervals: sleepIntervals,
... ... @@ -1192,6 +1429,12 @@ class OHOSHealthRawDataCoreService {
final score = calculation.score;
final state = calculation.state;
if (merged == null || score == null || state == null) {
_profileLog(
'sleep_day_finish userId=$userId '
'date=${_dateKeyFromDateTime(day)} hasResult=false '
'reason=missing_score_or_state '
'elapsedMs=${dayStopwatch.elapsedMilliseconds}',
);
_logInfo(
'$_sleepCalcLogMarker skip_invalid userId=$userId '
'date=${_dateKeyFromDateTime(day)} '
... ... @@ -1201,6 +1444,12 @@ class OHOSHealthRawDataCoreService {
continue;
}
if (!calculation.hasValidSleep) {
_profileLog(
'sleep_day_finish userId=$userId '
'date=${_dateKeyFromDateTime(day)} hasResult=false '
'reason=no_valid_sleep '
'elapsedMs=${dayStopwatch.elapsedMilliseconds}',
);
_logInfo(
'$_sleepCalcLogMarker skip_invalid userId=$userId '
'date=${_dateKeyFromDateTime(day)} reason=no_valid_sleep '
... ... @@ -1210,6 +1459,12 @@ class OHOSHealthRawDataCoreService {
}
if (latestSleepResultTime != null &&
merged.endTime <= latestSleepResultTime) {
_profileLog(
'sleep_day_finish userId=$userId '
'date=${_dateKeyFromDateTime(day)} hasResult=false '
'reason=existing mergedEnd=${merged.endTime} '
'elapsedMs=${dayStopwatch.elapsedMilliseconds}',
);
_logInfo(
'$_sleepCalcLogMarker skip_existing userId=$userId '
'date=${_dateKeyFromDateTime(day)} mergedEnd=${merged.endTime} '
... ... @@ -1230,6 +1485,12 @@ class OHOSHealthRawDataCoreService {
uploaded: false,
),
);
_profileLog(
'sleep_day_finish userId=$userId '
'date=${_dateKeyFromDateTime(day)} hasResult=true '
'sleepMinutes=${calculation.summary.sleepMinutes} '
'elapsedMs=${dayStopwatch.elapsedMilliseconds}',
);
_logInfo(
'$_sleepCalcLogMarker result userId=$userId '
'date=${_dateKeyFromDateTime(day)} start=${merged.startTime} '
... ... @@ -1237,7 +1498,12 @@ class OHOSHealthRawDataCoreService {
'sleepMinutes=${calculation.summary.sleepMinutes}',
);
}
final sleepStoreStopwatch = Stopwatch()..start();
await _localStore.upsertSleepResults(userId: userId, results: results);
_profileLog(
'sleep_store_finish userId=$userId stored=${results.length} '
'elapsedMs=${sleepStoreStopwatch.elapsedMilliseconds}',
);
_logInfo(
'$_sleepCalcLogMarker stored userId=$userId stored=${results.length}',
);
... ... @@ -1392,6 +1658,16 @@ class OHOSHealthRawDataCoreService {
return DateTime(date.year, date.month, date.day);
}
static int _twoMonthLookbackStart(int endTime) {
final endDate = DateTime.fromMillisecondsSinceEpoch(endTime * 1000);
return DateTime(
endDate.year,
endDate.month - defaultLookbackMonths,
endDate.day,
).millisecondsSinceEpoch ~/
1000;
}
static int _dateKeyFromDateTime(DateTime date) {
return date.year * 10000 + date.month * 100 + date.day;
}
... ... @@ -1437,6 +1713,42 @@ class OHOSHealthRawDataCoreService {
// Logger may be unavailable in isolated unit tests.
}
}
static void _profileLog(String message) {
if (!kDebugMode) return;
_logInfo('$_profileLogMarker $message');
}
static void _publishCalculationEvent({
required OhosHealthRawDataPipelineEventType type,
required String flow,
required int userId,
required int startTime,
required int endTime,
int? elapsedMs,
int? hrvCount,
int? realtimeCount,
int? dailyCount,
int? sleepCount,
String? error,
}) {
OhosHealthRawDataPipelineEvents.publish(
OhosHealthRawDataPipelineEvent(
type: type,
flow: flow,
occurredAt: DateTime.now(),
userId: userId,
startTime: startTime,
endTime: endTime,
elapsedMs: elapsedMs,
hrvCount: hrvCount,
realtimeCount: realtimeCount,
dailyCount: dailyCount,
sleepCount: sleepCount,
error: error,
),
);
}
}
class _OhosStoredCalculationResult {
... ...
import 'dart:async';
enum OhosHealthRawDataPipelineEventType {
syncStarted,
syncSucceeded,
syncFailed,
calculationStarted,
calculationSucceeded,
calculationFailed,
}
class OhosHealthRawDataPipelineEvent {
const OhosHealthRawDataPipelineEvent({
required this.type,
required this.flow,
required this.occurredAt,
this.userId,
this.dataType,
this.dataTypes,
this.startTime,
this.endTime,
this.elapsedMs,
this.pageCount,
this.storedCount,
this.hrvCount,
this.realtimeCount,
this.dailyCount,
this.sleepCount,
this.error,
});
final OhosHealthRawDataPipelineEventType type;
final String flow;
final DateTime occurredAt;
final int? userId;
final int? dataType;
final List<int>? dataTypes;
final int? startTime;
final int? endTime;
final int? elapsedMs;
final int? pageCount;
final int? storedCount;
final int? hrvCount;
final int? realtimeCount;
final int? dailyCount;
final int? sleepCount;
final String? error;
bool get isStarted =>
type == OhosHealthRawDataPipelineEventType.syncStarted ||
type == OhosHealthRawDataPipelineEventType.calculationStarted;
bool get isSucceeded =>
type == OhosHealthRawDataPipelineEventType.syncSucceeded ||
type == OhosHealthRawDataPipelineEventType.calculationSucceeded;
bool get isFailed =>
type == OhosHealthRawDataPipelineEventType.syncFailed ||
type == OhosHealthRawDataPipelineEventType.calculationFailed;
}
class OhosHealthRawDataPipelineEvents {
OhosHealthRawDataPipelineEvents._();
static final StreamController<OhosHealthRawDataPipelineEvent> _controller =
StreamController<OhosHealthRawDataPipelineEvent>.broadcast();
static Stream<OhosHealthRawDataPipelineEvent> get stream =>
_controller.stream;
static void publish(OhosHealthRawDataPipelineEvent event) {
if (_controller.isClosed) return;
_controller.add(event);
}
}
... ...
... ... @@ -108,39 +108,53 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore {
);
return 0;
}
final storage = await _prepareRawDataStorage(db, dataType);
final itemsToStore = await _filterExistingDuplicates(
db: db,
dataType: dataType,
storage: storage,
items: filteredItems,
latestTime: latestTime,
);
if (itemsToStore.isEmpty) {
_log(
'skip_store_existing_duplicates dataType=$dataType '
'latestTime=$latestTime incoming=${items.length} '
'candidates=${filteredItems.length}',
);
return 0;
}
var storedCount = 0;
await db.transaction((txn) async {
if (dataType == OhosHealthRawDataType.sleepAnalysis) {
for (final item in filteredItems) {
final rowId = await txn.insert(
final batch = txn.batch();
for (final item in itemsToStore) {
batch.insert(
sleepDataTable,
_sleepRow(item, createTime),
conflictAlgorithm: ConflictAlgorithm.ignore,
);
if (rowId > 0) storedCount += 1;
}
storedCount += await _commitInsertBatch(batch);
return;
}
if (dataType == OhosHealthRawDataType.workout) {
final table = _rawDataTable(dataType);
await _createRawIntervalDataTypeTable(txn, table);
for (final item in filteredItems) {
final rowId = await txn.insert(
table,
final batch = txn.batch();
for (final item in itemsToStore) {
batch.insert(
storage.table,
_intervalRow(item, createTime),
conflictAlgorithm: ConflictAlgorithm.ignore,
);
if (rowId > 0) storedCount += 1;
}
storedCount += await _commitInsertBatch(batch);
return;
}
final storedDataType = _storedHealthDataType(dataType);
final table = _rawDataTable(storedDataType);
await _createRawDataTypeTable(txn, table);
for (final item in filteredItems) {
final rowId = await txn.insert(
table,
final batch = txn.batch();
for (final item in itemsToStore) {
batch.insert(
storage.table,
_rawRow(item, createTime),
conflictAlgorithm: _dailyItemNeedsRefresh(
dataType: dataType,
... ... @@ -150,10 +164,10 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore {
? ConflictAlgorithm.replace
: ConflictAlgorithm.ignore,
);
if (rowId > 0) storedCount += 1;
}
storedCount += await _commitInsertBatch(batch);
});
final skippedCount = filteredItems.length - storedCount;
final skippedCount = filteredItems.length - itemsToStore.length;
if (skippedCount > 0) {
_log(
'skip_store_duplicates dataType=$dataType latestTime=$latestTime '
... ... @@ -164,6 +178,104 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore {
return storedCount;
}
Future<_RawDataStorage> _prepareRawDataStorage(
Database db,
int dataType,
) async {
if (dataType == OhosHealthRawDataType.sleepAnalysis) {
return const _RawDataStorage(
table: sleepDataTable,
keyColumn: 'from_time',
);
}
if (dataType == OhosHealthRawDataType.workout) {
final table = _rawDataTable(dataType);
await _createRawIntervalDataTypeTable(db, table);
return _RawDataStorage(table: table, keyColumn: 'from_time');
}
final storedDataType = _storedHealthDataType(dataType);
final table = _rawDataTable(storedDataType);
await _createRawDataTypeTable(db, table);
return _RawDataStorage(table: table, keyColumn: 'time');
}
Future<List<OhosHealthRawDataItem>> _filterExistingDuplicates({
required Database db,
required int dataType,
required _RawDataStorage storage,
required List<OhosHealthRawDataItem> items,
required int? latestTime,
}) async {
final keyedItems = <int, OhosHealthRawDataItem>{};
final refreshKeys = <int>{};
for (final item in items) {
final keyTime = _dedupeKeyTime(dataType: dataType, item: item);
keyedItems.putIfAbsent(keyTime, () => item);
if (_dailyItemNeedsRefresh(
dataType: dataType,
item: item,
latestTime: latestTime,
)) {
refreshKeys.add(keyTime);
}
}
if (keyedItems.isEmpty) return const <OhosHealthRawDataItem>[];
if (latestTime == null) {
return keyedItems.values.toList(growable: false);
}
final existingKeys = await _queryExistingKeys(
db: db,
table: storage.table,
keyColumn: storage.keyColumn,
keys: keyedItems.keys.where((key) => !refreshKeys.contains(key)),
);
return <OhosHealthRawDataItem>[
for (final entry in keyedItems.entries)
if (refreshKeys.contains(entry.key) ||
!existingKeys.contains(entry.key))
entry.value,
];
}
Future<Set<int>> _queryExistingKeys({
required Database db,
required String table,
required String keyColumn,
required Iterable<int> keys,
}) async {
final keyList = keys.toList(growable: false);
if (keyList.isEmpty) return const <int>{};
final existing = <int>{};
const chunkSize = 900;
for (var offset = 0; offset < keyList.length; offset += chunkSize) {
final chunk =
keyList.skip(offset).take(chunkSize).toList(growable: false);
final placeholders = List<String>.filled(chunk.length, '?').join(',');
final rows = await db.query(
table,
columns: <String>[keyColumn],
where: '$keyColumn IN ($placeholders)',
whereArgs: chunk,
);
for (final row in rows) {
final key = row[keyColumn];
if (key is int) {
existing.add(key);
} else if (key is num) {
existing.add(key.toInt());
}
}
}
return existing;
}
Future<int> _commitInsertBatch(Batch batch) async {
final results = await batch.commit(noResult: false);
return results.whereType<int>().where((rowId) => rowId > 0).length;
}
@override
Future<List<OhosHealthRawDataItem>> queryRawData({
required int dataType,
... ... @@ -606,3 +718,13 @@ CREATE TABLE IF NOT EXISTS $table (
return value is num ? value.toInt() : null;
}
}
class _RawDataStorage {
const _RawDataStorage({
required this.table,
required this.keyColumn,
});
final String table;
final String keyColumn;
}
... ...
import 'dart:async';
import 'dart:collection';
import 'dart:math' as math;
import 'package:dio/dio.dart';
import 'package:doublefeel_flutter/core/error/app_error.dart';
import 'package:doublefeel_flutter/core/logging/app_logger.dart';
import 'package:doublefeel_flutter/core/result/app_result.dart';
import 'package:doublefeel_flutter/data/models/enums/app_enums.dart';
... ... @@ -10,6 +14,7 @@ import 'package:doublefeel_flutter/data/models/health/health_v2_models.dart';
import 'package:flutter/foundation.dart';
import 'huawei_health_data_type.dart';
import 'ohos_health_raw_data_events.dart';
class OhosHealthRawDataType {
const OhosHealthRawDataType._();
... ... @@ -25,17 +30,20 @@ class OhosHealthRawDataSyncService {
OhosHealthRawDataLocalStore? localStore,
DateTime Function()? nowProvider,
void Function(String message)? logSink,
int maxConcurrentFetches = defaultMaxConcurrentFetches,
}) : _remoteDataSource =
remoteDataSource ?? const TodoOhosHealthRawDataRemoteDataSource(),
_localStore = localStore ?? const TodoOhosHealthRawDataLocalStore(),
_nowProvider = nowProvider ?? DateTime.now,
_logSink = logSink;
_logSink = logSink,
_fetchLimiter = _AsyncLimiter(maxConcurrentFetches);
static const int defaultLookbackDays = 183;
static const int incrementalBackfillDays = 7;
static const int defaultLookbackMonths = 2;
static const int heartRateFetchChunkDays = 10;
static const int defaultFetchChunkDays = 30;
static const int defaultMaxConcurrentFetches = 10;
static const String logMarker = '[OHOS_HEALTH_RAW_SYNC]';
static const String profileLogMarker = '[OHOS_HEALTH_RAW_PROFILE]';
static final List<int> calculationDataTypes = List<int>.unmodifiable(
<int>[
for (final type in HuaweiHealthDataType.values) type.dataType,
... ... @@ -48,6 +56,7 @@ class OhosHealthRawDataSyncService {
final OhosHealthRawDataLocalStore _localStore;
final DateTime Function() _nowProvider;
final void Function(String message)? _logSink;
final _AsyncLimiter _fetchLimiter;
final Map<String, Future<OhosHealthRawDataSyncResult>> _runningSyncs =
<String, Future<OhosHealthRawDataSyncResult>>{};
... ... @@ -56,17 +65,22 @@ class OhosHealthRawDataSyncService {
int? startTime,
int? endTime,
}) async {
final totalStopwatch = Stopwatch()..start();
final resolvedEndTime = endTime ?? _unixSeconds(_nowProvider());
final resolvedStartTime = await _resolveStartTime(
dataType: dataType,
requestedStartTime: startTime,
endTime: resolvedEndTime,
);
_profileLog(
'syncRawData_resolved dataType=$dataType '
'requestedStartTime=${startTime ?? ''} startTime=$resolvedStartTime '
'endTime=$resolvedEndTime elapsedMs=${totalStopwatch.elapsedMilliseconds}',
);
if (resolvedEndTime < resolvedStartTime) {
throw ArgumentError.value(endTime, 'endTime');
}
final syncKey = '$dataType:$resolvedStartTime:$resolvedEndTime';
final running = _runningSyncs[syncKey];
if (running != null) {
... ... @@ -74,9 +88,21 @@ class OhosHealthRawDataSyncService {
'sync_duplicate_join dataType=$dataType '
'startTime=$resolvedStartTime endTime=$resolvedEndTime',
);
_profileLog(
'syncRawData_duplicate_join dataType=$dataType '
'startTime=$resolvedStartTime endTime=$resolvedEndTime',
);
return running;
}
_publishSyncEvent(
type: OhosHealthRawDataPipelineEventType.syncStarted,
flow: 'syncRawData',
dataType: dataType,
startTime: resolvedStartTime,
endTime: resolvedEndTime,
);
final task = _syncResolvedRawData(
dataType: dataType,
startTime: resolvedStartTime,
... ... @@ -84,12 +110,43 @@ class OhosHealthRawDataSyncService {
);
_runningSyncs[syncKey] = task;
try {
return await task;
final result = await task;
_profileLog(
'syncRawData_finish dataType=$dataType '
'storedCount=${result.storedCount} pageCount=${result.pageCount} '
'segmentCount=${result.segmentCount} '
'elapsedMs=${totalStopwatch.elapsedMilliseconds}',
);
_publishSyncEvent(
type: OhosHealthRawDataPipelineEventType.syncSucceeded,
flow: 'syncRawData',
dataType: dataType,
startTime: resolvedStartTime,
endTime: resolvedEndTime,
elapsedMs: totalStopwatch.elapsedMilliseconds,
pageCount: result.pageCount,
storedCount: result.storedCount,
);
return result;
} catch (error, stackTrace) {
_log(
'sync_failed dataType=$dataType '
'startTime=$resolvedStartTime endTime=$resolvedEndTime '
'error=$error stackTrace=$stackTrace',
'error=${_describeError(error)} stackTrace=$stackTrace',
);
_profileLog(
'syncRawData_failed dataType=$dataType '
'elapsedMs=${totalStopwatch.elapsedMilliseconds} '
'error=${_describeError(error)}',
);
_publishSyncEvent(
type: OhosHealthRawDataPipelineEventType.syncFailed,
flow: 'syncRawData',
dataType: dataType,
startTime: resolvedStartTime,
endTime: resolvedEndTime,
elapsedMs: totalStopwatch.elapsedMilliseconds,
error: _describeError(error),
);
rethrow;
} finally {
... ... @@ -108,21 +165,34 @@ class OhosHealthRawDataSyncService {
int? endTime,
List<int>? dataTypes,
}) async {
final totalStopwatch = Stopwatch()..start();
final resolvedEndTime = endTime ?? _unixSeconds(_nowProvider());
final earliestStartTime = _twoMonthLookbackStart(resolvedEndTime);
final eventStartTime = startTime == null
? earliestStartTime
: math.max(startTime, earliestStartTime);
final resolvedDataTypes = dataTypes ?? calculationDataTypes;
_log(
'calculation_sync_start dataTypes=${resolvedDataTypes.join(',')} '
'startTime=${startTime ?? ''} endTime=$resolvedEndTime',
);
_publishSyncEvent(
type: OhosHealthRawDataPipelineEventType.syncStarted,
flow: 'syncCalculationRawData',
dataTypes: resolvedDataTypes,
startTime: eventStartTime,
endTime: resolvedEndTime,
);
final List<OhosHealthRawDataSyncResult> results;
try {
final activityGoalFuture = _remoteDataSource.fetchActivityGoal();
final rawFetchesFuture = Future.wait(
final activityGoalFuture = _fetchAndStoreActivityGoalForCalculationSync();
final rawSyncFuture = Future.wait(
resolvedDataTypes.map(
(dataType) => _fetchRawDataForSync(
(dataType) => _syncRawDataForCalculation(
dataType: dataType,
startTime: startTime,
endTime: resolvedEndTime,
storeGate: activityGoalFuture,
),
),
eagerError: true,
... ... @@ -130,29 +200,30 @@ class OhosHealthRawDataSyncService {
final fetched = await Future.wait<Object?>(
<Future<Object?>>[
activityGoalFuture,
rawFetchesFuture,
rawSyncFuture,
],
eagerError: true,
);
final activityGoal = fetched[0] as V2ActivityTarget?;
final rawFetches = fetched[1] as List<_FetchedRawDataSync>;
if (activityGoal != null) {
await _localStore.upsertActivityGoal(activityGoal);
_log(
'calculation_sync_activity_goal_stored '
'move=${activityGoal.move} step=${activityGoal.step} '
'exercise=${activityGoal.exercise} stand=${activityGoal.stand}',
);
}
results = <OhosHealthRawDataSyncResult>[];
for (final fetch in rawFetches) {
results.add(await _storeFetchedRawData(fetch));
}
results = fetched[1] as List<OhosHealthRawDataSyncResult>;
} catch (error, stackTrace) {
_log(
'calculation_sync_failed dataTypes=${resolvedDataTypes.join(',')} '
'startTime=${startTime ?? ''} endTime=$resolvedEndTime '
'error=$error stackTrace=$stackTrace',
'error=${_describeError(error)} stackTrace=$stackTrace',
);
_profileLog(
'calculationSync_failed dataTypes=${resolvedDataTypes.join(',')} '
'elapsedMs=${totalStopwatch.elapsedMilliseconds} '
'error=${_describeError(error)}',
);
_publishSyncEvent(
type: OhosHealthRawDataPipelineEventType.syncFailed,
flow: 'syncCalculationRawData',
dataTypes: resolvedDataTypes,
startTime: eventStartTime,
endTime: resolvedEndTime,
elapsedMs: totalStopwatch.elapsedMilliseconds,
error: _describeError(error),
);
rethrow;
}
... ... @@ -168,14 +239,69 @@ class OhosHealthRawDataSyncService {
'calculation_sync_finish dataTypes=${resolvedDataTypes.join(',')} '
'pageCount=$pageCount storedCount=$storedCount',
);
_profileLog(
'calculationSync_finish dataTypes=${resolvedDataTypes.join(',')} '
'pageCount=$pageCount storedCount=$storedCount '
'elapsedMs=${totalStopwatch.elapsedMilliseconds}',
);
_publishSyncEvent(
type: OhosHealthRawDataPipelineEventType.syncSucceeded,
flow: 'syncCalculationRawData',
dataTypes: resolvedDataTypes,
startTime: eventStartTime,
endTime: resolvedEndTime,
elapsedMs: totalStopwatch.elapsedMilliseconds,
pageCount: pageCount,
storedCount: storedCount,
);
return results;
}
Future<_FetchedRawDataSync> _fetchRawDataForSync({
Future<V2ActivityTarget?> _fetchActivityGoalForCalculationSync() async {
final stopwatch = Stopwatch()..start();
try {
final result = await _remoteDataSource.fetchActivityGoal();
_profileLog(
'calculationSync_fetchActivityGoal_finish found=${result != null} '
'elapsedMs=${stopwatch.elapsedMilliseconds}',
);
return result;
} catch (error) {
_profileLog(
'calculationSync_fetchActivityGoal_failed '
'elapsedMs=${stopwatch.elapsedMilliseconds} '
'error=${_describeError(error)}',
);
rethrow;
}
}
Future<V2ActivityTarget?>
_fetchAndStoreActivityGoalForCalculationSync() async {
final activityGoal = await _fetchActivityGoalForCalculationSync();
if (activityGoal != null) {
final storeGoalStopwatch = Stopwatch()..start();
await _localStore.upsertActivityGoal(activityGoal);
_profileLog(
'calculationSync_storeActivityGoal elapsedMs='
'${storeGoalStopwatch.elapsedMilliseconds}',
);
_log(
'calculation_sync_activity_goal_stored '
'move=${activityGoal.move} step=${activityGoal.step} '
'exercise=${activityGoal.exercise} stand=${activityGoal.stand}',
);
}
return activityGoal;
}
Future<OhosHealthRawDataSyncResult> _syncRawDataForCalculation({
required int dataType,
required int? startTime,
required int endTime,
required Future<void> storeGate,
}) async {
final stopwatch = Stopwatch()..start();
final resolvedStartTime = await _resolveStartTime(
dataType: dataType,
requestedStartTime: startTime,
... ... @@ -184,11 +310,33 @@ class OhosHealthRawDataSyncService {
if (endTime < resolvedStartTime) {
throw ArgumentError.value(endTime, 'endTime');
}
return _fetchResolvedRawData(
dataType: dataType,
startTime: resolvedStartTime,
endTime: endTime,
_profileLog(
'fetchRawDataForSync_resolved dataType=$dataType '
'requestedStartTime=${startTime ?? ''} startTime=$resolvedStartTime '
'endTime=$endTime resolveElapsedMs=${stopwatch.elapsedMilliseconds}',
);
try {
final result = await _syncResolvedRawData(
dataType: dataType,
startTime: resolvedStartTime,
endTime: endTime,
storeGate: storeGate,
);
_profileLog(
'fetchRawDataForSync_finish dataType=$dataType '
'segments=${result.segmentCount} pageCount=${result.pageCount} '
'storedCount=${result.storedCount} '
'elapsedMs=${stopwatch.elapsedMilliseconds}',
);
return result;
} catch (error) {
_profileLog(
'fetchRawDataForSync_failed dataType=$dataType '
'elapsedMs=${stopwatch.elapsedMilliseconds} '
'error=${_describeError(error)}',
);
rethrow;
}
}
Future<List<OhosHealthRawDataItem>> queryRawData({
... ... @@ -218,7 +366,8 @@ class OhosHealthRawDataSyncService {
}
} catch (error, stackTrace) {
_log(
'activity_goal_remote_failed error=$error stackTrace=$stackTrace',
'activity_goal_remote_failed error=${_describeError(error)} '
'stackTrace=$stackTrace',
);
}
}
... ... @@ -235,20 +384,9 @@ class OhosHealthRawDataSyncService {
required int dataType,
required int startTime,
required int endTime,
Future<void>? storeGate,
}) async {
final fetched = await _fetchResolvedRawData(
dataType: dataType,
startTime: startTime,
endTime: endTime,
);
return _storeFetchedRawData(fetched);
}
Future<_FetchedRawDataSync> _fetchResolvedRawData({
required int dataType,
required int startTime,
required int endTime,
}) async {
final stopwatch = Stopwatch()..start();
final fetchRanges = _splitIntoFetchRanges(
dataType: dataType,
startTime: startTime,
... ... @@ -261,9 +399,9 @@ class OhosHealthRawDataSyncService {
'chunkDays=$fetchChunkDays rangeCount=${fetchRanges.length}',
);
final segments = await Future.wait(
<Future<_FetchedRawDataSegment>>[
for (var index = 0; index < fetchRanges.length; index++)
final pending = <Future<_FetchedRawDataSegmentOutcome>>[
for (var index = 0; index < fetchRanges.length; index++)
_trackedSegmentFetch(
_fetchRawDataSegment(
dataType: dataType,
range: fetchRanges[index],
... ... @@ -271,16 +409,97 @@ class OhosHealthRawDataSyncService {
segmentCount: fetchRanges.length,
fetchChunkDays: fetchChunkDays,
),
],
eagerError: true,
),
];
var pageCount = 0;
var fetchedItems = 0;
var storedCount = 0;
int? earliestStoredTime;
try {
while (pending.isNotEmpty) {
final outcome = await Future.any(pending);
pending.remove(outcome.task);
final error = outcome.error;
if (error != null) {
Error.throwWithStackTrace(
error, outcome.stackTrace ?? StackTrace.current);
}
final segment = outcome.segment!;
final page = segment.page;
pageCount += 1;
fetchedItems += page.items.length;
if (page.items.isNotEmpty) {
if (storeGate != null) await storeGate;
final pageStoreStopwatch = Stopwatch()..start();
final pageStoredCount = await _localStore.upsertRawDataBatch(
dataType: dataType,
items: page.items,
);
storedCount += pageStoredCount;
if (pageStoredCount > 0) {
for (final item in page.items) {
final keyTime = _dedupeKeyTime(dataType: dataType, item: item);
earliestStoredTime = earliestStoredTime == null
? keyTime
: math.min(earliestStoredTime, keyTime);
}
}
_profileLog(
'page_store_finish dataType=$dataType '
'segment=${segment.segmentIndex + 1}/${fetchRanges.length} '
'fetchedItems=${page.items.length} storedItems=$pageStoredCount '
'elapsedMs=${pageStoreStopwatch.elapsedMilliseconds}',
);
_log(
'page_stored dataType=$dataType '
'segment=${segment.segmentIndex + 1}/${fetchRanges.length} '
'fetchedItems=${page.items.length} '
'storedItems=$pageStoredCount totalStored=$storedCount',
);
}
_log(
'segment_finish dataType=$dataType '
'segment=${segment.segmentIndex + 1}/${fetchRanges.length}',
);
}
} catch (error) {
_profileLog(
'syncResolvedRawData_failed dataType=$dataType '
'rangeCount=${fetchRanges.length} '
'elapsedMs=${stopwatch.elapsedMilliseconds} '
'error=${_describeError(error)}',
);
rethrow;
}
_log(
'sync_finish dataType=$dataType '
'startTime=$startTime endTime=$endTime '
'chunkDays=$fetchChunkDays rangeCount=${fetchRanges.length} '
'pageCount=$pageCount '
'storedCount=$storedCount',
);
segments.sort((a, b) => a.segmentIndex.compareTo(b.segmentIndex));
return _FetchedRawDataSync(
_profileLog(
'syncResolvedRawData_finish dataType=$dataType '
'pageCount=$pageCount fetchedItems=$fetchedItems '
'storedCount=$storedCount '
'elapsedMs=${stopwatch.elapsedMilliseconds}',
);
_profileLog(
'storeFetchedRawData_finish dataType=$dataType '
'pageCount=$pageCount fetchedItems=$fetchedItems '
'storedCount=$storedCount '
'elapsedMs=${stopwatch.elapsedMilliseconds}',
);
return OhosHealthRawDataSyncResult(
dataType: dataType,
startTime: startTime,
endTime: endTime,
fetchChunkDays: fetchChunkDays,
segments: segments,
segmentCount: fetchRanges.length,
pageCount: pageCount,
storedCount: storedCount,
earliestStoredTime: earliestStoredTime,
);
}
... ... @@ -291,6 +510,8 @@ class OhosHealthRawDataSyncService {
required int segmentCount,
required int fetchChunkDays,
}) async {
final stopwatch = Stopwatch()..start();
var queueWaitMs = 0;
_log(
'segment_start dataType=$dataType '
'segment=${segmentIndex + 1}/$segmentCount '
... ... @@ -301,16 +522,49 @@ class OhosHealthRawDataSyncService {
'apiEndDate=${_exclusiveEndDateKeyFromUnixSeconds(range.endTime)}',
);
final page = await _remoteDataSource.fetchRawDataPage(
dataType: dataType,
startTime: range.startTime,
endTime: range.endTime,
);
final OhosHealthRawDataPage page;
try {
page = await _fetchLimiter.run(
() {
queueWaitMs = stopwatch.elapsedMilliseconds;
_profileLog(
'segment_fetch_acquired dataType=$dataType '
'segment=${segmentIndex + 1}/$segmentCount '
'queueWaitMs=$queueWaitMs activeFetches='
'${_fetchLimiter.activeCount}',
);
return _remoteDataSource.fetchRawDataPage(
dataType: dataType,
startTime: range.startTime,
endTime: range.endTime,
);
},
);
} catch (error) {
_profileLog(
'segment_fetch_failed dataType=$dataType '
'segment=${segmentIndex + 1}/$segmentCount '
'startTime=${range.startTime} endTime=${range.endTime} '
'queueWaitMs=$queueWaitMs '
'requestElapsedMs=${stopwatch.elapsedMilliseconds - queueWaitMs} '
'elapsedMs=${stopwatch.elapsedMilliseconds} '
'error=${_describeError(error)}',
);
rethrow;
}
_log(
'page_fetched dataType=$dataType '
'segment=${segmentIndex + 1}/$segmentCount '
'items=${page.items.length}',
);
_profileLog(
'segment_fetch_finish dataType=$dataType '
'segment=${segmentIndex + 1}/$segmentCount '
'startTime=${range.startTime} endTime=${range.endTime} '
'items=${page.items.length} queueWaitMs=$queueWaitMs '
'requestElapsedMs=${stopwatch.elapsedMilliseconds - queueWaitMs} '
'elapsedMs=${stopwatch.elapsedMilliseconds}',
);
return _FetchedRawDataSegment(
segmentIndex: segmentIndex,
... ... @@ -319,52 +573,23 @@ class OhosHealthRawDataSyncService {
);
}
Future<OhosHealthRawDataSyncResult> _storeFetchedRawData(
_FetchedRawDataSync fetched,
) async {
var pageCount = 0;
var storedCount = 0;
for (final segment in fetched.segments) {
final page = segment.page;
pageCount += 1;
if (page.items.isNotEmpty) {
final pageStoredCount = await _localStore.upsertRawDataBatch(
dataType: fetched.dataType,
items: page.items,
);
storedCount += pageStoredCount;
_log(
'page_stored dataType=${fetched.dataType} '
'segment=${segment.segmentIndex + 1}/${fetched.segmentCount} '
'fetchedItems=${page.items.length} '
'storedItems=$pageStoredCount totalStored=$storedCount',
);
}
_log(
'segment_finish dataType=${fetched.dataType} '
'segment=${segment.segmentIndex + 1}/${fetched.segmentCount}',
);
}
_log(
'sync_finish dataType=${fetched.dataType} '
'startTime=${fetched.startTime} endTime=${fetched.endTime} '
'chunkDays=${fetched.fetchChunkDays} rangeCount=${fetched.segmentCount} '
'pageCount=$pageCount '
'storedCount=$storedCount',
);
return OhosHealthRawDataSyncResult(
dataType: fetched.dataType,
startTime: fetched.startTime,
endTime: fetched.endTime,
segmentCount: fetched.segmentCount,
pageCount: pageCount,
storedCount: storedCount,
earliestStoredTime: storedCount > 0 ? fetched.earliestItemTime : null,
Future<_FetchedRawDataSegmentOutcome> _trackedSegmentFetch(
Future<_FetchedRawDataSegment> future,
) {
late final Future<_FetchedRawDataSegmentOutcome> tracked;
tracked = future.then(
(segment) => _FetchedRawDataSegmentOutcome(
task: tracked,
segment: segment,
),
onError: (Object error, StackTrace stackTrace) =>
_FetchedRawDataSegmentOutcome(
task: tracked,
error: error,
stackTrace: stackTrace,
),
);
return tracked;
}
Future<int> _resolveStartTime({
... ... @@ -373,22 +598,18 @@ class OhosHealthRawDataSyncService {
required int endTime,
}) async {
final latestDataTime = await _localStore.latestDataTime(dataType: dataType);
final earliestStartTime = _twoMonthLookbackStart(endTime);
final int baseStartTime;
if (latestDataTime != null) {
final backfillStart = math.max(
0,
latestDataTime - incrementalBackfillDays * Duration.secondsPerDay,
);
baseStartTime = requestedStartTime == null
? backfillStart
: math.min(requestedStartTime, backfillStart);
? latestDataTime
: math.min(requestedStartTime, latestDataTime);
} else {
baseStartTime = requestedStartTime ??
_unixSeconds(
_nowProvider().subtract(const Duration(days: defaultLookbackDays)),
);
baseStartTime = requestedStartTime ?? earliestStartTime;
}
final resolvedStartTime = _startOfLocalDay(baseStartTime);
final resolvedStartTime = _startOfLocalDay(
math.max(baseStartTime, earliestStartTime),
);
return resolvedStartTime > endTime ? endTime : resolvedStartTime;
}
... ... @@ -401,6 +622,12 @@ class OhosHealthRawDataSyncService {
return _unixSeconds(DateTime(dateTime.year, dateTime.month, dateTime.day));
}
int _twoMonthLookbackStart(int endTime) {
final endDate = DateTime.fromMillisecondsSinceEpoch(endTime * 1000);
return _unixSeconds(DateTime(
endDate.year, endDate.month - defaultLookbackMonths, endDate.day));
}
List<OhosHealthRawDataFetchRange> _splitIntoFetchRanges({
required int dataType,
required int startTime,
... ... @@ -450,6 +677,40 @@ class OhosHealthRawDataSyncService {
// AppLogger may not be initialized in isolated test/bootstrap contexts.
}
}
void _profileLog(String message) {
if (!kDebugMode) return;
_log('$profileLogMarker $message');
}
void _publishSyncEvent({
required OhosHealthRawDataPipelineEventType type,
required String flow,
int? dataType,
List<int>? dataTypes,
int? startTime,
int? endTime,
int? elapsedMs,
int? pageCount,
int? storedCount,
String? error,
}) {
OhosHealthRawDataPipelineEvents.publish(
OhosHealthRawDataPipelineEvent(
type: type,
flow: flow,
occurredAt: _nowProvider(),
dataType: dataType,
dataTypes: dataTypes == null ? null : List<int>.unmodifiable(dataTypes),
startTime: startTime,
endTime: endTime,
elapsedMs: elapsedMs,
pageCount: pageCount,
storedCount: storedCount,
error: error,
),
);
}
}
abstract class OhosHealthRawDataRemoteDataSource {
... ... @@ -528,7 +789,7 @@ class OhosHarmonyHealthRawDataRemoteDataSource
),
),
AppFailure<HmSleepData>(:final error) => throw StateError(
'OHOS sleep raw data fetch failed: $error',
'OHOS sleep raw data fetch failed: ${_describeAppError(error)}',
),
};
}
... ... @@ -541,7 +802,7 @@ class OhosHarmonyHealthRawDataRemoteDataSource
),
),
AppFailure<HmWorkoutData>(:final error) => throw StateError(
'OHOS workout raw data fetch failed: $error',
'OHOS workout raw data fetch failed: ${_describeAppError(error)}',
),
};
}
... ... @@ -560,7 +821,7 @@ class OhosHarmonyHealthRawDataRemoteDataSource
),
),
AppFailure<HmHealthData>(:final error) => throw StateError(
'OHOS health raw data fetch failed: $error',
'OHOS health raw data fetch failed: ${_describeAppError(error)}',
),
};
}
... ... @@ -571,7 +832,7 @@ class OhosHarmonyHealthRawDataRemoteDataSource
return switch (result) {
AppSuccess<V2ActivityTarget>(:final data) => data,
AppFailure<V2ActivityTarget>(:final error) => throw StateError(
'OHOS activity goal fetch failed: $error',
'OHOS activity goal fetch failed: ${_describeAppError(error)}',
),
};
}
... ... @@ -726,35 +987,6 @@ class OhosHealthRawDataFetchRange {
final int endTime;
}
class _FetchedRawDataSync {
const _FetchedRawDataSync({
required this.dataType,
required this.startTime,
required this.endTime,
required this.fetchChunkDays,
required this.segments,
});
final int dataType;
final int startTime;
final int endTime;
final int fetchChunkDays;
final List<_FetchedRawDataSegment> segments;
int get segmentCount => segments.length;
int? get earliestItemTime {
int? earliest;
for (final segment in segments) {
for (final item in segment.page.items) {
final keyTime = _dedupeKeyTime(dataType: dataType, item: item);
earliest = earliest == null ? keyTime : math.min(earliest, keyTime);
}
}
return earliest;
}
}
class _FetchedRawDataSegment {
const _FetchedRawDataSegment({
required this.segmentIndex,
... ... @@ -767,6 +999,20 @@ class _FetchedRawDataSegment {
final OhosHealthRawDataPage page;
}
class _FetchedRawDataSegmentOutcome {
const _FetchedRawDataSegmentOutcome({
required this.task,
this.segment,
this.error,
this.stackTrace,
});
final Future<_FetchedRawDataSegmentOutcome> task;
final _FetchedRawDataSegment? segment;
final Object? error;
final StackTrace? stackTrace;
}
class OhosHealthRawDataSyncResult {
const OhosHealthRawDataSyncResult({
required this.dataType,
... ... @@ -832,3 +1078,79 @@ int _exclusiveEndDateKeyFromUnixSeconds(int seconds) {
int _dateKeyFromDateTime(DateTime dateTime) {
return dateTime.year * 10000 + dateTime.month * 100 + dateTime.day;
}
String _describeError(Object error) {
if (error is AppError) return _describeAppError(error);
return error.toString();
}
String _describeAppError(AppError error) {
return switch (error) {
AppNetworkError() => _describeAppNetworkError(error),
AppHttpError(
:final statusCode,
:final businessCode,
:final businessMessage,
:final cause,
) =>
'AppHttpError(statusCode=$statusCode, businessCode=$businessCode, '
'businessMessage=$businessMessage, cause=$cause)',
AppCancelledError() => 'AppCancelledError',
AppUnknownError(:final cause) => 'AppUnknownError(cause=$cause)',
};
}
String _describeAppNetworkError(AppNetworkError error) {
final cause = error.cause;
if (cause is DioException) {
return 'AppNetworkError('
'dioType=${cause.type}, '
'message=${cause.message}, '
'uri=${cause.requestOptions.uri}, '
'method=${cause.requestOptions.method}, '
'statusCode=${cause.response?.statusCode}'
')';
}
return 'AppNetworkError(cause=$cause)';
}
class _AsyncLimiter {
_AsyncLimiter(this._maxConcurrent) {
if (_maxConcurrent <= 0) {
throw ArgumentError.value(_maxConcurrent, 'maxConcurrentFetches');
}
}
final int _maxConcurrent;
final Queue<Completer<void>> _waiters = Queue<Completer<void>>();
int _activeCount = 0;
int get activeCount => _activeCount;
Future<T> run<T>(Future<T> Function() action) async {
await _acquire();
try {
return await action();
} finally {
_release();
}
}
Future<void> _acquire() {
if (_activeCount < _maxConcurrent) {
_activeCount += 1;
return Future<void>.value();
}
final waiter = Completer<void>();
_waiters.add(waiter);
return waiter.future;
}
void _release() {
if (_waiters.isNotEmpty) {
_waiters.removeFirst().complete();
return;
}
_activeCount -= 1;
}
}
... ...
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import '../../../logging/app_logger.dart';
class OhosSqliteWriteBenchmark {
const OhosSqliteWriteBenchmark();
static const logMarker = '[OHOS_SQLITE_BENCH]';
Future<String> run() async {
final totalStopwatch = Stopwatch()..start();
final tempDir = await getTemporaryDirectory();
final path =
'${tempDir.path}/ohos_sqlite_write_bench_${DateTime.now().millisecondsSinceEpoch}.sqlite';
final db = await openDatabase(path);
final results = <_BenchmarkResult>[];
try {
_log('start path=$path');
results.add(await _runBatchInsert(
db: db,
table: 'batch_false_500',
rowCount: 500,
noResult: false,
));
results.add(await _runBatchInsert(
db: db,
table: 'batch_false_1000',
rowCount: 1000,
noResult: false,
));
results.add(await _runBatchInsert(
db: db,
table: 'batch_false_2000',
rowCount: 2000,
noResult: false,
));
results.add(await _runBatchInsert(
db: db,
table: 'batch_true_500',
rowCount: 500,
noResult: true,
));
results.add(await _runBatchInsert(
db: db,
table: 'batch_true_1000',
rowCount: 1000,
noResult: true,
));
results.add(await _runBatchInsert(
db: db,
table: 'batch_true_2000',
rowCount: 2000,
noResult: true,
));
results.add(await _runChunkedBatchInsert(
db: db,
table: 'chunked_true_2000',
rowCount: 2000,
chunkSize: 500,
));
results.add(await _runExistingKeyQuery(
db: db,
table: 'existing_key_query_2000',
rowCount: 2000,
chunkSize: 900,
));
} finally {
await db.close();
try {
await File(path).delete();
} catch (_) {
// Best-effort cleanup for a debug-only benchmark database.
}
}
final lines = <String>[
'OHOS SQLite 写入性能测试完成,总耗时 ${totalStopwatch.elapsedMilliseconds}ms',
for (final result in results) result.summary,
];
final report = lines.join('\n');
_log('finish totalMs=${totalStopwatch.elapsedMilliseconds}');
return report;
}
Future<_BenchmarkResult> _runBatchInsert({
required Database db,
required String table,
required int rowCount,
required bool noResult,
}) async {
await _createRawTable(db, table);
final stopwatch = Stopwatch()..start();
await db.transaction((txn) async {
final batch = txn.batch();
for (var index = 0; index < rowCount; index += 1) {
batch.insert(
table,
_row(index),
conflictAlgorithm: ConflictAlgorithm.ignore,
);
}
await batch.commit(noResult: noResult);
});
return _finish(
name: 'batch(noResult:$noResult)',
rowCount: rowCount,
elapsedMs: stopwatch.elapsedMilliseconds,
);
}
Future<_BenchmarkResult> _runChunkedBatchInsert({
required Database db,
required String table,
required int rowCount,
required int chunkSize,
}) async {
await _createRawTable(db, table);
final stopwatch = Stopwatch()..start();
for (var start = 0; start < rowCount; start += chunkSize) {
final end = (start + chunkSize).clamp(0, rowCount);
await db.transaction((txn) async {
final batch = txn.batch();
for (var index = start; index < end; index += 1) {
batch.insert(
table,
_row(index),
conflictAlgorithm: ConflictAlgorithm.ignore,
);
}
await batch.commit(noResult: true);
});
}
return _finish(
name: 'chunkedBatch(noResult:true,chunk:$chunkSize)',
rowCount: rowCount,
elapsedMs: stopwatch.elapsedMilliseconds,
);
}
Future<_BenchmarkResult> _runExistingKeyQuery({
required Database db,
required String table,
required int rowCount,
required int chunkSize,
}) async {
await _createRawTable(db, table);
await db.transaction((txn) async {
final batch = txn.batch();
for (var index = 0; index < rowCount; index += 1) {
batch.insert(table, _row(index),
conflictAlgorithm: ConflictAlgorithm.ignore);
}
await batch.commit(noResult: true);
});
final keys = List<int>.generate(rowCount, (index) => 1780000000 + index);
final stopwatch = Stopwatch()..start();
var found = 0;
for (var offset = 0; offset < keys.length; offset += chunkSize) {
final chunk = keys.skip(offset).take(chunkSize).toList(growable: false);
final placeholders = List<String>.filled(chunk.length, '?').join(',');
final rows = await db.query(
table,
columns: const <String>['time'],
where: 'time IN ($placeholders)',
whereArgs: chunk,
);
found += rows.length;
}
final elapsedMs = stopwatch.elapsedMilliseconds;
_log(
'result name=existingKeyQuery rows=$rowCount found=$found '
'chunkSize=$chunkSize elapsedMs=$elapsedMs',
);
return _BenchmarkResult(
name: 'existingKeyQuery(chunk:$chunkSize)',
rowCount: rowCount,
elapsedMs: elapsedMs,
);
}
Future<void> _createRawTable(Database db, String table) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS $table (
id INTEGER PRIMARY KEY AUTOINCREMENT,
time INTEGER NOT NULL,
value REAL,
is_asleep INTEGER,
date_key INTEGER NOT NULL DEFAULT 0,
create_time INTEGER NOT NULL,
UNIQUE (time)
)
''');
await db.execute(
'CREATE INDEX IF NOT EXISTS idx_${table}_time ON $table(time)');
}
Map<String, Object?> _row(int index) {
final time = 1780000000 + index;
return <String, Object?>{
'time': time,
'value': 60 + index % 80,
'is_asleep': index % 5 == 0 ? 1 : 0,
'date_key': 20260801 + index ~/ 1000,
'create_time': 1780000000,
};
}
_BenchmarkResult _finish({
required String name,
required int rowCount,
required int elapsedMs,
}) {
final rowsPerSecond =
elapsedMs == 0 ? rowCount * 1000.0 : rowCount * 1000 / elapsedMs;
_log(
'result name=$name rows=$rowCount elapsedMs=$elapsedMs '
'rowsPerSecond=${rowsPerSecond.toStringAsFixed(1)}',
);
return _BenchmarkResult(
name: name,
rowCount: rowCount,
elapsedMs: elapsedMs,
rowsPerSecond: rowsPerSecond,
);
}
void _log(String message) {
final tagged = '$logMarker $message';
debugPrint(tagged);
try {
AppLogger.i(tagged);
} catch (_) {
// AppLogger may not be initialized in isolated debug/bootstrap contexts.
}
}
}
class _BenchmarkResult {
const _BenchmarkResult({
required this.name,
required this.rowCount,
required this.elapsedMs,
this.rowsPerSecond,
});
final String name;
final int rowCount;
final int elapsedMs;
final double? rowsPerSecond;
String get summary {
final speed = rowsPerSecond;
if (speed == null) return '$name: $rowCount 条,${elapsedMs}ms';
return '$name: $rowCount 条,${elapsedMs}ms,${speed.toStringAsFixed(1)} 条/s';
}
}
... ...
... ... @@ -6,7 +6,7 @@ packages:
description:
name: _fe_analyzer_shared
sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "85.0.0"
analyzer:
... ... @@ -14,7 +14,7 @@ packages:
description:
name: analyzer
sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.7.1"
archive:
... ... @@ -22,7 +22,7 @@ packages:
description:
name: archive
sha256: ace891da0862b0e4cabbb064ee3fd87b2728b898949fdb366d83fe98342c9f19
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.2.0"
args:
... ... @@ -30,63 +30,79 @@ packages:
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
name: async
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
url: "https://pub.dev"
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.13.1"
version: "2.11.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
version: "2.1.1"
build:
dependency: transitive
description:
name: build
sha256: "825fed4d63050252a0b6e74f2d75844c4a85b664814be6993bd3493fb5239779"
url: "https://pub.dev"
sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.1"
version: "2.4.2"
build_config:
dependency: transitive
description:
name: build_config
sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
url: "https://pub.dev"
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0"
version: "1.1.2"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78
url: "https://pub.dev"
sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.1.2"
version: "4.0.4"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.4"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "4e54dbeefdc70691ba80b3bce3976af63b5425c8c07dface348dfee664a0edc1"
url: "https://pub.dev"
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.15"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.9.0"
version: "8.0.0"
built_collection:
dependency: transitive
description:
name: built_collection
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.1.1"
built_value:
... ... @@ -94,7 +110,7 @@ packages:
description:
name: built_value
sha256: f87ea98192116f7093cb214551ce1929caae0681fdba282b3d8b4462adee7bb7
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "8.13.0"
cached_network_image:
... ... @@ -102,7 +118,7 @@ packages:
description:
name: cached_network_image
sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.4.1"
cached_network_image_platform_interface:
... ... @@ -110,7 +126,7 @@ packages:
description:
name: cached_network_image_platform_interface
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.1.1"
cached_network_image_web:
... ... @@ -118,103 +134,95 @@ packages:
description:
name: cached_network_image_web
sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.1"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.0"
version: "1.3.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
url: "https://pub.dev"
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.4"
version: "2.0.3"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: cfd4f5f575a49c5f10ca856e9846073f1e6c3ee94912377eea5f6cefc5272941
url: "https://pub.dev"
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
version: "1.1.1"
code_builder:
dependency: transitive
description:
name: code_builder
sha256: aa5932e94c6c39c2f9ec4e5e06dfdd11a9430a61f6c41b6ba75b28ce0c481baf
url: "https://pub.dev"
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.12.0"
version: "4.10.1"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.19.1"
version: "1.19.0"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.2"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: f141ea4f277af142a0356955707f6556f37b03947d39d55585981a06ca437bd6
url: "https://pub.dev"
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.3.5+5"
version: "0.3.4+2"
crypto:
dependency: "direct main"
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.7"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
url: "https://pub.dev"
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.9"
version: "1.0.8"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb"
url: "https://pub.dev"
sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.1"
version: "3.0.1"
dio:
dependency: "direct main"
description:
name: dio
sha256: "852ec3b48cc431ac04fff978413c541502b67ffc3e26921e74e3d994694192c1"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.11.1"
dio_web_adapter:
... ... @@ -222,7 +230,7 @@ packages:
description:
name: dio_web_adapter
sha256: "3a1b2cd7be71086f38504956e3ebcd2837288d231ff454bafa78021244102bfc"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.2"
equatable:
... ... @@ -230,71 +238,71 @@ packages:
description:
name: equatable
sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.3"
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
url: "https://pub.dev"
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.0"
version: "2.1.3"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.1"
file_selector_linux:
dependency: transitive
description:
name: file_selector_linux
sha256: da76400e7872ce7637ffdce12749ec24169c25f6195c28372208e65a24bcd2ab
url: "https://pub.dev"
sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.9.4+1"
version: "0.9.3+2"
file_selector_macos:
dependency: transitive
description:
name: file_selector_macos
sha256: d57c62362766b5e7ae739448650b66c6aab7a68ba7ecc65e04018652645ae0f4
url: "https://pub.dev"
sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.9.5+1"
version: "0.9.4+3"
file_selector_platform_interface:
dependency: transitive
description:
name: file_selector_platform_interface
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
url: "https://pub.dev"
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.7.0"
version: "2.6.2"
file_selector_windows:
dependency: transitive
description:
name: file_selector_windows
sha256: fbefc5fb92c6d3cbe8d284a2cd971b593bb07d2cd6da8557b81a862250b4acec
url: "https://pub.dev"
sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.9.3+6"
version: "0.9.3+4"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.1"
fl_chart:
... ... @@ -302,7 +310,7 @@ packages:
description:
name: fl_chart
sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.70.2"
flutter:
... ... @@ -314,16 +322,16 @@ packages:
dependency: transitive
description:
name: flutter_cache_manager
sha256: "1de7849213b4c73c85aca7e0ac687a9a5d82ccdb594366b9dcc26cb6a2189cd2"
url: "https://pub.dev"
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.4.2"
version: "3.4.1"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.0.0"
flutter_localizations:
... ... @@ -335,10 +343,10 @@ packages:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"
url: "https://pub.dev"
sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.35"
version: "2.0.29"
flutter_test:
dependency: "direct dev"
description: flutter
... ... @@ -349,7 +357,7 @@ packages:
description:
name: flutter_timezone
sha256: "869677426fde92dbe170fb7d2d4929f2a8343c2f5f62f08b0bb64f908630b073"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.1.0"
flutter_web_plugins:
... ... @@ -366,12 +374,20 @@ packages:
url: "https://gitcode.com/CPF-Flutter/flutter_fluttertoast.git"
source: git
version: "9.0.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.0"
get:
dependency: "direct main"
description:
name: get
sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.7.3"
glob:
... ... @@ -379,7 +395,7 @@ packages:
description:
name: glob
sha256: "218aeb56050c714f62a3182775320dfa04602b55074873e24e31bbd39bda96fb"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.0"
graphs:
... ... @@ -387,23 +403,15 @@ packages:
description:
name: graphs
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.2"
hooks:
dependency: transitive
description:
name: hooks
sha256: eaac480a35ec0814146c2c48d96aaa829e0e44a7662c88ae84c9edf4bc35651f
url: "https://pub.dev"
source: hosted
version: "2.2.0"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.6.0"
http_multi_server:
... ... @@ -411,7 +419,7 @@ packages:
description:
name: http_multi_server
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.2.2"
http_parser:
... ... @@ -419,7 +427,7 @@ packages:
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.1.2"
image_cropper:
... ... @@ -435,7 +443,7 @@ packages:
dependency: transitive
description:
path: image_cropper_for_web
ref: b1b45b1a5333571095569d53d01720c505fff983
ref: "9.1.0-ohos-1.0.0-beta.1"
resolved-ref: b1b45b1a5333571095569d53d01720c505fff983
url: "https://gitcode.com/CPF-Flutter/fluttertpc_image_cropper.git"
source: git
... ... @@ -444,7 +452,7 @@ packages:
dependency: transitive
description:
path: image_cropper_platform_interface
ref: b1b45b1a5333571095569d53d01720c505fff983
ref: "9.1.0-ohos-1.0.0-beta.1"
resolved-ref: b1b45b1a5333571095569d53d01720c505fff983
url: "https://gitcode.com/CPF-Flutter/fluttertpc_image_cropper.git"
source: git
... ... @@ -462,42 +470,42 @@ packages:
dependency: transitive
description:
name: image_picker_android
sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f
url: "https://pub.dev"
sha256: e83b2b05141469c5e19d77e1dfa11096b6b1567d09065b2265d7c6904560050c
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.8.13+17"
version: "0.8.13"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
url: "https://pub.dev"
sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.1"
version: "3.1.0"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
sha256: ee3885b6fcd71958fbc79770dd194c63371439d536d69c47b279171a486482ae
url: "https://pub.dev"
sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.8.13+7"
version: "0.8.13"
image_picker_linux:
dependency: transitive
description:
name: image_picker_linux
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.2"
image_picker_macos:
dependency: transitive
description:
name: image_picker_macos
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
url: "https://pub.dev"
sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.2+1"
version: "0.2.2"
image_picker_ohos:
dependency: transitive
description:
... ... @@ -511,96 +519,80 @@ packages:
dependency: transitive
description:
name: image_picker_platform_interface
sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
url: "https://pub.dev"
sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.11.1"
version: "2.11.0"
image_picker_windows:
dependency: transitive
description:
name: image_picker_windows
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.20.2"
version: "0.19.0"
io:
dependency: transitive
description:
name: io
sha256: "2635216ca6a737e60de577ffa1a48a0bec76ca8a62917cfc1bb88c14c570646f"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
jni:
dependency: transitive
description:
name: jni
sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3
url: "https://pub.dev"
source: hosted
version: "1.0.3"
jni_flutter:
js:
dependency: transitive
description:
name: jni_flutter
sha256: b2310cdd4c18c65c081ab141a41efa94aa26c65431803703ece51996f174f351
url: "https://pub.dev"
name: js
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.3"
jni_util:
dependency: transitive
description:
name: jni_util
sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
version: "0.7.1"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
url: "https://pub.dev"
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.12.0"
version: "4.9.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
url: "https://pub.flutter-io.cn"
source: hosted
version: "11.0.2"
version: "10.0.7"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.10"
version: "3.0.8"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.2"
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.1.1"
logger:
... ... @@ -608,7 +600,7 @@ packages:
description:
name: logger
sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.7.0"
logging:
... ... @@ -616,7 +608,7 @@ packages:
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0"
lottie:
... ... @@ -624,23 +616,23 @@ packages:
description:
name: lottie
sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.3.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.12.17"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.11.1"
meta:
... ... @@ -648,7 +640,7 @@ packages:
description:
name: meta
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.19.0"
mime:
... ... @@ -656,23 +648,15 @@ packages:
description:
name: mime
sha256: bd47de35f07e27267e69c8c8b22edf9473bfee170a60d60fcc93730c5144b7f6
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: ad56fd53a78ff6b1472fa59ff2a4e8b8ccabafc586fc263a1dfad0b99b5553e3
url: "https://pub.dev"
source: hosted
version: "9.6.0"
octo_image:
dependency: transitive
description:
name: octo_image
sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
package_config:
... ... @@ -680,17 +664,17 @@ packages:
description:
name: package_config
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.9.1"
version: "1.9.0"
path_provider:
dependency: "direct main"
description:
... ... @@ -704,26 +688,26 @@ packages:
dependency: transitive
description:
name: path_provider_android
sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
url: "https://pub.dev"
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.1"
version: "2.2.17"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.dev"
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.6.0"
version: "2.4.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"
url: "https://pub.dev"
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.2"
version: "2.2.1"
path_provider_ohos:
dependency: transitive
description:
... ... @@ -737,16 +721,16 @@ packages:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
url: "https://pub.dev"
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.3"
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.0"
permission_handler:
... ... @@ -754,7 +738,7 @@ packages:
description:
name: permission_handler
sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "11.4.0"
permission_handler_android:
... ... @@ -762,7 +746,7 @@ packages:
description:
name: permission_handler_android
sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "12.1.0"
permission_handler_apple:
... ... @@ -770,7 +754,7 @@ packages:
description:
name: permission_handler_apple
sha256: f49cb15a064ea9d974fc7fbb302099353b7b170d07284e86e264561579e5bcf8
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "9.6.1"
permission_handler_html:
... ... @@ -778,7 +762,7 @@ packages:
description:
name: permission_handler_html
sha256: "6ea98b3f17f60d3b527f2647ed2ab4dc0f6bfe25b22cb1c363f5d8f62252f6ac"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.4+1"
permission_handler_ohos:
... ... @@ -795,7 +779,7 @@ packages:
description:
name: permission_handler_platform_interface
sha256: a5c8a97ecf5616112a5b16d4b8e9ec0e5ae90ef63ac69c0d7b8ae240be760b23
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.4.0"
permission_handler_windows:
... ... @@ -803,7 +787,7 @@ packages:
description:
name: permission_handler_windows
sha256: caeae01858a0a7d2df67a445ac98e1ad95e55a0e77c73044f4e9b1c8c2289cbd
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.2"
pigeon:
... ... @@ -820,7 +804,7 @@ packages:
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.6"
plugin_platform_interface:
... ... @@ -828,7 +812,7 @@ packages:
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.8"
pool:
... ... @@ -836,7 +820,7 @@ packages:
description:
name: pool
sha256: "4177f68c237ea2128d1bee66ac17b2ce05ba3dbaafcbdd54c5d40a39d0b6b11c"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.5.3"
posix:
... ... @@ -844,7 +828,7 @@ packages:
description:
name: posix
sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.5.2"
pretty_dio_logger:
... ... @@ -852,7 +836,7 @@ packages:
description:
name: pretty_dio_logger
sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.0"
pub_semver:
... ... @@ -860,31 +844,23 @@ packages:
description:
name: pub_semver
sha256: "261236774e8b1d69cfc6b9eabbc96c40f25e7a2d6b171f3385d4f65d5734fb24"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.1"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: c38b81cbf34450b67e0265d73433569d12e34782e30ed769c9cc99c9d5f2e796
url: "https://pub.dev"
source: hosted
version: "1.6.0"
record_use:
dependency: transitive
description:
name: record_use
sha256: "1cb8564af8d43b464294411db9217f5ec04891c6f22ee2c32d73ae05e88a6bd2"
url: "https://pub.dev"
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.1"
version: "1.5.0"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.28.0"
share_plus:
... ... @@ -900,7 +876,7 @@ packages:
dependency: transitive
description:
path: "packages/share_plus/share_plus_platform_interface"
ref: "55de300a8627c55cd45ac86e6a26bcae8e0ca4cf"
ref: "br_share_plus-v10.1.1_ohos"
resolved-ref: "55de300a8627c55cd45ac86e6a26bcae8e0ca4cf"
url: "https://gitcode.com/CPF-Flutter/flutter_plus_plugins.git"
source: git
... ... @@ -918,24 +894,24 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
url: "https://pub.dev"
sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.23"
version: "2.4.11"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "2ec3934efa51e46117f23031cc141b8fc878e8525b94ec1ea4f7f586cf1b47ea"
url: "https://pub.dev"
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.7"
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.1"
shared_preferences_ohos:
... ... @@ -951,16 +927,16 @@ packages:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
url: "https://pub.dev"
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.2"
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.3"
shared_preferences_windows:
... ... @@ -968,7 +944,7 @@ packages:
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.1"
shelf:
... ... @@ -976,7 +952,7 @@ packages:
description:
name: shelf
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.2"
shelf_web_socket:
... ... @@ -984,7 +960,7 @@ packages:
description:
name: shelf_web_socket
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.0"
simple_gesture_detector:
... ... @@ -992,7 +968,7 @@ packages:
description:
name: simple_gesture_detector
sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.1"
sky_engine:
... ... @@ -1004,10 +980,10 @@ packages:
dependency: transitive
description:
name: source_span
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
url: "https://pub.dev"
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.10.2"
version: "1.10.0"
sqflite:
dependency: "direct main"
description:
... ... @@ -1021,31 +997,31 @@ packages:
dependency: transitive
description:
name: sqflite_android
sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40"
url: "https://pub.dev"
sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.2+3"
version: "2.4.0"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465"
url: "https://pub.dev"
sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.8"
version: "2.5.4+6"
sqflite_darwin:
dependency: transitive
description:
name: sqflite_darwin
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
url: "https://pub.dev"
sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.2"
version: "2.4.1+1"
sqflite_ohos:
dependency: transitive
description:
path: sqflite_ohos
ref: "8cc162bdd90adad2e8c8054b52fc14c1e86000ee"
ref: "2.4.2-ohos-1.0.0-beta.2"
resolved-ref: "8cc162bdd90adad2e8c8054b52fc14c1e86000ee"
url: "https://gitcode.com/CPF-Flutter/flutter_sqflite.git"
source: git
... ... @@ -1055,159 +1031,167 @@ packages:
description:
name: sqflite_platform_interface
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.12.1"
version: "1.12.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.4"
version: "2.1.2"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: a00e5f18bffc764f923e7dec1038527f7fe7a1791361a7117f0358193f13d53a
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.1"
version: "1.3.0"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
url: "https://pub.dev"
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.4.0"
version: "3.3.0+3"
table_calendar:
dependency: "direct main"
description:
name: table_calendar
sha256: f276347cad425ef837a41e8d9ad43f3ee7d59227aa4c36d7430607a5a18fa3b3
url: "https://pub.dev"
sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.2.1"
version: "3.1.3"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.2"
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.7.7"
version: "0.7.3"
thinking_analytics:
dependency: "direct main"
description:
name: thinking_analytics
sha256: b01cac0b5482e71c1d75c44c77d27f427662cc65a77b7bc3c8b49617d7a01e02
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.3.3"
timing:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.2"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.0"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: "10f86fef4c2c43563fa6c211ff9cf757adf4d3ab762c56bd430664a947d70cd0"
url: "https://pub.dev"
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.2.3"
version: "3.2.1"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
url: "https://pub.dev"
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.3"
version: "2.4.1"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "6c5ad3f22cd4c38e089b81963b3cd7bb83b111b2df5dce008bb066162f42e429"
url: "https://pub.dev"
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.6"
version: "3.1.4"
uuid:
dependency: transitive
description:
name: uuid
sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.6.0"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.0"
version: "2.1.4"
video_thumbnail:
dependency: "direct main"
description:
name: video_thumbnail
sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.5.6"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0"
url: "https://pub.dev"
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
url: "https://pub.flutter-io.cn"
source: hosted
version: "15.3.0"
version: "14.3.0"
watcher:
dependency: transitive
description:
name: watcher
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.1"
web:
... ... @@ -1215,7 +1199,7 @@ packages:
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.1"
web_socket:
... ... @@ -1223,7 +1207,7 @@ packages:
description:
name: web_socket
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.1"
web_socket_channel:
... ... @@ -1231,7 +1215,7 @@ packages:
description:
name: web_socket_channel
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.3"
webview_flutter:
... ... @@ -1247,8 +1231,8 @@ packages:
dependency: transitive
description:
path: "packages/webview_flutter/webview_flutter_android"
ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9"
resolved-ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9"
ref: "br_webview_flutter-v4.13.0_ohos"
resolved-ref: "3f44aceb6b076a6ec58f3570435f1907411511fe"
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
source: git
version: "4.7.0"
... ... @@ -1256,8 +1240,8 @@ packages:
dependency: transitive
description:
path: "packages/webview_flutter/webview_flutter_ohos"
ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9"
resolved-ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9"
ref: "br_webview_flutter-v4.13.0_ohos"
resolved-ref: "3f44aceb6b076a6ec58f3570435f1907411511fe"
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
source: git
version: "4.7.0"
... ... @@ -1265,8 +1249,8 @@ packages:
dependency: transitive
description:
path: "packages/webview_flutter/webview_flutter_platform_interface"
ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9"
resolved-ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9"
ref: "br_webview_flutter-v4.13.0_ohos"
resolved-ref: "3f44aceb6b076a6ec58f3570435f1907411511fe"
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
source: git
version: "2.13.1"
... ... @@ -1274,8 +1258,8 @@ packages:
dependency: transitive
description:
path: "packages/webview_flutter/webview_flutter_wkwebview"
ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9"
resolved-ref: "5a9d0055772c7973712c0eaaf2de0e5f0749f8b9"
ref: "br_webview_flutter-v4.13.0_ohos"
resolved-ref: "3f44aceb6b076a6ec58f3570435f1907411511fe"
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
source: git
version: "3.22.0"
... ... @@ -1283,16 +1267,16 @@ packages:
dependency: transitive
description:
name: win32
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
url: "https://pub.dev"
sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.15.0"
version: "5.10.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
yaml:
... ... @@ -1300,9 +1284,9 @@ packages:
description:
name: yaml
sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea
url: "https://pub.dev"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.4"
sdks:
dart: ">=3.10.3 <4.0.0"
flutter: ">=3.38.4"
dart: ">=3.6.2 <4.0.0"
flutter: ">=3.27.0"
... ...
... ... @@ -2,6 +2,7 @@ import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_dat
import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_models.dart';
import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ios/apple_health_raw_data_core_service.dart';
import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_core_service.dart';
import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_events.dart';
import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_sync_service.dart';
import 'package:doublefeel_flutter/data/models/enums/app_enums.dart';
import 'package:doublefeel_flutter/data/models/health/health_v2_models.dart';
... ... @@ -249,7 +250,7 @@ void main() {
store.dailyStressPoints.map((point) => point.date), contains(20260830));
});
test('OHOS core backfills missing daily stress from local raw data anchor',
test('OHOS core calculates from latest local raw data day without backfill',
() async {
final backfillDay = DateTime(2026, 8, 30);
final backfillBase =
... ... @@ -308,20 +309,63 @@ void main() {
call.dataType == HealthDataUploadType.hrv.type ||
call.dataType == HealthDataUploadType.heartRate.type)
.map((call) => _dateKey(call.startTime)),
everyElement(lessThanOrEqualTo(20260827)),
everyElement(20260903),
);
expect(
store.hrvStressPoints.any((point) => point.rawEndTime == backfillBase),
isTrue,
isFalse,
);
expect(
store.realtimeStressPoints.any(
(point) => _dateKey(point.rawEndTime) == 20260830,
),
isTrue,
isFalse,
);
expect(result.dailyStressPoints.map((point) => point.date),
contains(20260830));
isNot(contains(20260830)));
});
test('OHOS core publishes calculation start and success events', () async {
final events = <OhosHealthRawDataPipelineEvent>[];
final subscription = OhosHealthRawDataPipelineEvents.stream.listen(
events.add,
);
addTearDown(subscription.cancel);
final base = DateTime.now()
.subtract(const Duration(days: 2))
.millisecondsSinceEpoch ~/
1000;
final service = OHOSHealthRawDataCoreService(
rawDataSource: _FakeHealthRawDataSource(
pointsByDataType: {
HealthDataUploadType.hrv.type: [_point(1, base, 60)],
HealthDataUploadType.heartRate.type: [_point(2, base, 80)],
},
),
localStore: _FakeHealthRawStressLocalStore(),
userIdProvider: () => 42,
uploadResultsAfterCalculation: false,
healthReadAuthorizationChecker: () async => false,
);
await service.syncAndStore(
startTime: base - Duration.secondsPerHour,
endTime: base + Duration.secondsPerHour,
readChunkDays: 1,
);
await Future<void>.delayed(Duration.zero);
final calculationEvents =
events.where((event) => event.flow == 'calculateAndStore').toList();
expect(
calculationEvents.map((event) => event.type),
containsAllInOrder([
OhosHealthRawDataPipelineEventType.calculationStarted,
OhosHealthRawDataPipelineEventType.calculationSucceeded,
]),
);
expect(calculationEvents.last.userId, 42);
expect(calculationEvents.last.hrvCount, 1);
});
test('OHOS core shows debug timing toast after sync and calculation',
... ...
... ... @@ -2,6 +2,7 @@ import 'dart:async';
import 'package:doublefeel_flutter/core/result/app_result.dart';
import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/huawei_health_data_type.dart';
import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_events.dart';
import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_sync_service.dart';
import 'package:doublefeel_flutter/data/models/harmony/hm_health_data.dart';
import 'package:doublefeel_flutter/data/models/harmony/hm_sleep_data.dart';
... ... @@ -47,7 +48,7 @@ void main() {
expect(local.storedBatches.map((e) => e.length), [2]);
});
test('syncRawData backfills recent days when local data already exists',
test('syncRawData starts from latest local data day without backfill',
() async {
final latest = _unixSeconds(DateTime(2026, 9, 3, 10));
final remote = _FakeOhosHealthRawDataRemoteDataSource([
... ... @@ -72,7 +73,7 @@ void main() {
endTime: _unixSeconds(DateTime(2026, 9, 3, 11)),
);
expect(_dateKey(remote.calls.single.startTime), 20260827);
expect(_dateKey(remote.calls.single.startTime), 20260903);
expect(result.storedCount, 1);
expect(result.earliestStoredTime, 1788019232);
});
... ... @@ -111,12 +112,12 @@ void main() {
endTime: _unixSeconds(DateTime(2026, 9, 4)),
);
expect(_dateKey(remote.calls[0].startTime), 20260827);
expect(_dateKey(remote.calls[1].startTime), 20260826);
expect(_dateKey(remote.calls[2].startTime), 20260825);
expect(_dateKey(remote.calls[0].startTime), 20260903);
expect(_dateKey(remote.calls[1].startTime), 20260902);
expect(_dateKey(remote.calls[2].startTime), 20260901);
});
test('syncRawData falls back to half-year lookback when local data is empty',
test('syncRawData falls back to two-month lookback when local data is empty',
() async {
final now = DateTime(2026, 8, 13, 12);
final remote = _FakeOhosHealthRawDataRemoteDataSource([
... ... @@ -131,13 +132,7 @@ void main() {
final result = await service.syncRawData(dataType: 7);
final fallback = now.subtract(
const Duration(
days: OhosHealthRawDataSyncService.defaultLookbackDays,
),
);
final expectedStart =
_unixSeconds(DateTime(fallback.year, fallback.month, fallback.day));
final expectedStart = _unixSeconds(DateTime(2026, 6, 13));
final expectedEnd = now.millisecondsSinceEpoch ~/ 1000;
expect(result.startTime, expectedStart);
... ... @@ -151,6 +146,76 @@ void main() {
expect(local.storedBatches, isEmpty);
});
test('syncRawData clamps requested and latest anchors to two months',
() async {
final endTime = _unixSeconds(DateTime(2026, 9, 4, 12));
final remote = _FakeOhosHealthRawDataRemoteDataSource([
const OhosHealthRawDataPage(items: <OhosHealthRawDataItem>[]),
const OhosHealthRawDataPage(items: <OhosHealthRawDataItem>[]),
const OhosHealthRawDataPage(items: <OhosHealthRawDataItem>[]),
]);
final local = _FakeOhosHealthRawDataLocalStore(
latestDataTime: _unixSeconds(DateTime(2026, 1, 1, 8)),
);
final service = OhosHealthRawDataSyncService(
remoteDataSource: remote,
localStore: local,
);
final result = await service.syncRawData(
dataType: 7,
startTime: _unixSeconds(DateTime(2026, 1, 1)),
endTime: endTime,
);
final expectedStart = _unixSeconds(DateTime(2026, 7, 4));
expect(result.startTime, expectedStart);
expect(remote.calls.first.startTime, expectedStart);
expect(remote.calls.last.endTime, endTime);
});
test('syncRawData publishes start and success events', () async {
final events = <OhosHealthRawDataPipelineEvent>[];
final subscription = OhosHealthRawDataPipelineEvents.stream.listen(
events.add,
);
addTearDown(subscription.cancel);
final remote = _FakeOhosHealthRawDataRemoteDataSource([
const OhosHealthRawDataPage(
items: [
OhosHealthRawDataItem(
dataType: 1,
dataTime: 1001,
payload: {'time': 1001, 'value': 45},
),
],
),
]);
final service = OhosHealthRawDataSyncService(
remoteDataSource: remote,
localStore: _FakeOhosHealthRawDataLocalStore(),
nowProvider: () => DateTime(2026, 9, 4),
);
await service.syncRawData(
dataType: 1,
startTime: 1000,
endTime: 2000,
);
await Future<void>.delayed(Duration.zero);
expect(
events.map((event) => event.type),
containsAllInOrder([
OhosHealthRawDataPipelineEventType.syncStarted,
OhosHealthRawDataPipelineEventType.syncSucceeded,
]),
);
expect(events.last.flow, 'syncRawData');
expect(events.last.dataType, 1);
expect(events.last.storedCount, 1);
});
test('syncRawData splits non-heart-rate ranges every 30 days and logs marker',
() async {
final start = _unixSeconds(DateTime(2026, 1, 30, 10));
... ... @@ -310,6 +375,38 @@ void main() {
expect(result.pageCount, 3);
});
test('syncRawData limits segment fetch concurrency to ten', () async {
final start = _unixSeconds(DateTime(2026, 1));
final end = _unixSeconds(DateTime(2026, 4, 30));
final gate = Completer<void>();
final remote = _FakeOhosHealthRawDataRemoteDataSource(
const <OhosHealthRawDataPage>[],
gate: gate,
);
final service = OhosHealthRawDataSyncService(
remoteDataSource: remote,
localStore: _FakeOhosHealthRawDataLocalStore(),
);
final sync = service.syncRawData(
dataType: HuaweiHealthDataType.heartRate.dataType,
startTime: start,
endTime: end,
);
for (var i = 0; i < 20 && remote.calls.length < 10; i += 1) {
await Future<void>.delayed(const Duration(milliseconds: 1));
}
expect(remote.calls, hasLength(10));
gate.complete();
final result = await sync;
expect(result.segmentCount, 12);
expect(result.pageCount, 12);
expect(remote.calls, hasLength(12));
});
test('syncRawData joins duplicate in-flight syncs', () async {
final gate = Completer<void>();
final remote = _FakeOhosHealthRawDataRemoteDataSource(
... ...