TrainingInfo.fromJson constructor

TrainingInfo.fromJson(
  1. Map<String, dynamic> json
)

Implementation

factory TrainingInfo.fromJson(Map<String, dynamic> json) {
  debugPrint("解析训练数据: $json");
  int startTime = json["startTime"] ?? -1;
  final endTime = json["endTime"] ?? -1;
  final validTime = json["validTime"] ?? -1;
  final steps = json["steps"] ?? -1;
  final distance = json["distance"] ?? -1;
  final rawHrList = List<int>.from((json["hrList"] ?? []).map((x) => x));

  // 检测并修复被截断的时间戳
  // 正确的时间戳应该是10位数字(Unix时间戳),如果少于10位则可能被截断
  if (startTime > 0 && startTime.toString().length < 10) {
    // 检测可能的截断情况
    final timeStr = startTime.toString();
    if (timeStr.length == 7) {
      // 如果是7位数字,可能是从10位时间戳的前7位,尝试补全
      // 例如:1756190 -> 1756190000 (添加3个0)
      startTime = startTime * 1000;
      print("检测到时间戳被截断,从 $timeStr 修复为 $startTime");
    } else if (timeStr.length == 8) {
      // 如果是8位数字,可能是从10位时间戳的前8位,尝试补全
      startTime = startTime * 100;
      print("检测到时间戳被截断,从 $timeStr 修复为 $startTime");
    } else if (timeStr.length == 9) {
      // 如果是9位数字,可能是从10位时间戳的前9位,尝试补全
      startTime = startTime * 10;
      print("检测到时间戳被截断,从 $timeStr 修复为 $startTime");
    }
  }

  // 将原始心率数据转换为 HeartRateData 对象列表
  List<HeartRateData> processedHrList = [];
  for (int i = 0; i < rawHrList.length; i++) {
    processedHrList.add(HeartRateData(
      timestamp: startTime + (i * 10), // 每10秒一个数据点
      hr: rawHrList[i],
    ));
  }

  // 计算最大数据点数
  final maxPoints10s = validTime > 0 ? (validTime / 10).ceil() : 0;
  final endTimestamp = startTime + validTime;

  // 处理步频数据
  List<int> finalCadencesPerMinute = []; // 每分钟数据
  List<int> finalCadencesPer10s = []; // 每10秒数据(详细分析用)
  List<CadenceData> processedCadencesList = [];

  // 统一处理来自 iOS(cadences) 或 Android(stepsList) 的每10秒数据
  final rawCadencesData = json["cadences"] ?? json["stepsList"];

  if (rawCadencesData != null) {
    // 统一处理来自 iOS(cadences) 或 Android(stepsList) 的每10秒数据
    final per10sData = List<int>.from(rawCadencesData.map((x) => x));

    // 根据实际训练时长截断数据
    final truncatedCadences = maxPoints10s > 0
        ? per10sData.take(maxPoints10s).toList()
        : per10sData;

    finalCadencesPer10s = truncatedCadences; // 保留原始10秒数据

    // 将每10秒数据转换为每分钟数据(每6个10秒数据合并为1个分钟数据)
    for (int i = 0; i < truncatedCadences.length; i += 6) {
      int sum = 0;

      // 取6个数据点求和(如果不足6个则取实际数量)
      for (int j = i; j < i + 6 && j < truncatedCadences.length; j++) {
        sum += truncatedCadences[j];
      }

      finalCadencesPerMinute.add(sum);
    }

    // 生成带时间戳的详细数据
    for (int i = 0; i < truncatedCadences.length; i++) {
      final timestamp = startTime + (i * 10);
      // 严格检查时间边界
      if (timestamp > endTimestamp) {
        break;
      }
      processedCadencesList.add(CadenceData(
        timestamp: timestamp,
        cadence: truncatedCadences[i],
      ));
    }
  } else if (validTime > 0 && steps > 0) {
    // 如果原生层没有提供步频数据,根据总步数和有效时间计算
    final cadencePerMinute = (steps * 60) ~/ validTime; // 每分钟步数
    final cadencePer10Seconds = (cadencePerMinute * 10) ~/ 60; // 每10秒步数
    final totalPointsPer10s = (validTime / 10).ceil(); // 总的10秒数据点数
    final totalPointsPerMinute = (validTime / 60).ceil(); // 总的分钟数据点数

    // 生成每分钟数据
    for (int i = 0; i < totalPointsPerMinute; i++) {
      // 为了模拟真实情况,在基础值上添加小幅波动(±5%)
      final baseValue = cadencePerMinute;
      final variation = (baseValue * 0.05).round(); // 5%变化
      final actualValue = baseValue + (i % 3 - 1) * variation; // 简单的波动模式
      final finalValue = actualValue > 0 ? actualValue : baseValue;

      finalCadencesPerMinute.add(finalValue);
    }

    // 生成每10秒详细数据
    for (int i = 0; i < totalPointsPer10s; i++) {
      final currentTimestamp = startTime + (i * 10);

      if (currentTimestamp > endTimestamp) {
        break;
      }
      // 为了模拟真实情况,在基础值上添加小幅波动(±5%)
      final baseValue = cadencePer10Seconds;
      final variation = (baseValue * 0.05).round(); // 5%变化
      final actualValue = baseValue + (i % 3 - 1) * variation; // 简单的波动模式
      final finalValue = actualValue > 0 ? actualValue : baseValue;

      finalCadencesPer10s.add(finalValue);
      processedCadencesList.add(CadenceData(
        timestamp: currentTimestamp,
        cadence: finalValue,
      ));
    }
  }

  // 处理步幅数据
  List<int> finalStridesPerMinute = []; // 每分钟数据
  List<int> finalStridesPer10s = []; // 每10秒数据(详细分析用)
  List<StrideData> processedStridesList = [];

  // 将 distanceList (Android) 和 strides (iOS) 视为同一类型的数据源
  final rawStridesData = json["strides"] ?? json["distanceList"];

  if (rawStridesData != null) {
    // 统一处理来自 iOS(strides) 或 Android(distanceList) 的每10秒数据
    final per10sData = List<int>.from(rawStridesData.map((x) => x));

    // 根据实际训练时长截断数据
    final truncatedStrides = maxPoints10s > 0
        ? per10sData.take(maxPoints10s).toList()
        : per10sData;
    finalStridesPer10s = truncatedStrides; // 保留原始10秒数据

    // 1. 直接使用原始的10秒数据生成带时间戳的 processedStridesList
    for (int i = 0; i < truncatedStrides.length; i++) {
      final timestamp = startTime + (i * 10);
      // 严格检查时间边界
      if (timestamp > endTimestamp) {
        break;
      }
      processedStridesList.add(StrideData(
        timestamp: timestamp,
        stride: truncatedStrides[i],
      ));
    }

    // 2. 从精确的10秒数据中,计算出每分钟的数据
    for (int i = 0; i < truncatedStrides.length; i += 6) {
      int sum = 0;
      // 取6个数据点求和(如果不足6个则取实际数量)
      for (int j = i; j < i + 6 && j < truncatedStrides.length; j++) {
        sum += truncatedStrides[j];
      }
      finalStridesPerMinute.add(sum);
    }
  } else if (validTime > 0 && distance > 0) {
    // 如果原生层没有提供步幅数据,根据总距离和有效时间计算
    final stridePerMinute = (distance * 60) ~/ validTime; // 每分钟距离(厘米)
    final stridePer10Seconds = (stridePerMinute * 10) ~/ 60; // 每10秒距离(厘米)
    final totalPointsPer10s = (validTime / 10).ceil(); // 总的10秒数据点数
    final totalPointsPerMinute = (validTime / 60).ceil(); // 总的分钟数据点数

    // 生成每分钟数据
    for (int i = 0; i < totalPointsPerMinute; i++) {
      // 为了模拟真实情况,在基础值上添加小幅波动(±8%)
      final baseValue = stridePerMinute;
      final variation = (baseValue * 0.08).round(); // 8%变化
      final actualValue = baseValue + (i % 4 - 1) * variation; // 简单的波动模式
      final finalValue = actualValue > 0 ? actualValue : baseValue;

      finalStridesPerMinute.add(finalValue);
    }

    // 生成每10秒详细数据
    for (int i = 0; i < totalPointsPer10s; i++) {
      final currentTimestamp = startTime + (i * 10);

      // 严格检查时间边界
      if (currentTimestamp > endTimestamp) {
        break;
      }
      // 为了模拟真实情况,在基础值上添加小幅波动(±8%)
      final baseValue = stridePer10Seconds;
      final variation = (baseValue * 0.08).round(); // 8%变化
      final actualValue = baseValue + (i % 4 - 1) * variation; // 简单的波动模式
      final finalValue = actualValue > 0 ? actualValue : baseValue;

      finalStridesPer10s.add(finalValue);
      processedStridesList.add(StrideData(
        timestamp: currentTimestamp,
        stride: finalValue,
      ));
    }
  }

  // 处理海拔数据 - 兼容iOS和Android层的字段名
  int? finalAvgElevation, finalMaxElevation, finalMinElevation;
  // iOS层字段:avgElevation, maxElevation, minElevation
  // Android层字段:averageAltitude, maxAltitude, minAltitude
  finalAvgElevation = json["avgElevation"] ?? json["averageAltitude"];
  finalMaxElevation = json["maxElevation"] ?? json["maxAltitude"];
  finalMinElevation = json["minElevation"] ?? json["minAltitude"];

  // 处理配速数据 - 兼容iOS和Android层的字段名,优先从JSON读取,如果没有则根据距离和时间计算
  // 处理配速数据 - 需要同时处理 s/m 和 s/km 两种单位
  int? finalAvgPace, finalMaxPace, finalMinPace; // 最终输出的 s/m 单位值
  int? initialAvgPaceInSm; // 初始平均配速,单位 s/m

  // 1. 获取或计算初始的平均配速 (单位: s/m),优先从JSON直接读取配速数据
  // iOS层字段:avgPace, maxPace, minPace
  // Android层字段:averageSpeed, maxSpeed, minSpeed
  if (json["avgPace"] != null || json["averageSpeed"] != null) {
    int avgPaceInSk = json["avgPace"] ?? json["averageSpeed"];
    initialAvgPaceInSm = (avgPaceInSk / 1000).round(); // 转换为 s/m
  } else if (distance > 0 && validTime > 0) {
    // 如果JSON中没有配速数据,根据总距离和总时间计算平均配速
    final distanceInMeters = distance / 100.0; // 转换为米(distance单位是厘米)
    if (distanceInMeters > 0) {
      initialAvgPaceInSm = (validTime / distanceInMeters).round(); // s/m
    }
  }

  // 2. 计算每10秒的详细配速列表(paceList,单位:s/km)
  List<PaceData> processedPaceList = [];

  // 基于每10秒步幅数据计算配速
  if (processedStridesList.isNotEmpty) {
    // 使用每10秒的详细步幅数据计算配速
    for (int i = 0; i < processedStridesList.length; i++) {
      final strideData = processedStridesList[i];
      final distance10sInMeter = strideData.stride / 100.0; // 厘米转换为米
      const double minEffectiveDistance = 0.1; // 0.1米 (10厘米)

      // --- 数据清洗:增加有效运动的最小距离阈值 ---
      // 如果10秒内移动距离过小(如小于10厘米),则视为静止,不计入有效配速计算

      if (distance10sInMeter > minEffectiveDistance) {
        // --- 计算 s/m 单位的配速 ---
        // 每米用时:10秒 / 距离米数 = 秒/米,公式:配速 = 时间 / 距离,衡量的是移动单位距离所需的时间
        final pacePerMeter = 10 / distance10sInMeter; // 秒/米
        // --- 计算 s/km 单位的配速 ---
        // 转换为每公里用时:秒/米 × 1000 = 秒/公里
        // 如需要每min/公里的单位值,除以60即可
        final pacePerKm = (pacePerMeter * 1000).round(); // 秒/公里
        // 应用合理的配速范围限制 (3分钟/公里 到 20分钟/公里)
        // int limitedPacePerKm = pacePerKm.round();
        // if (limitedPacePerKm < 180) limitedPacePerKm = 180; // 最快 3:00/km
        // if (limitedPacePerKm > 1200) limitedPacePerKm = 1200; // 最慢 20:00/km
        processedPaceList.add(PaceData(
          timestamp: strideData.timestamp,
          pace: pacePerKm, // limitedPacePerKm, // [需要限制范围的话使用此值]
        ));
      } else {
        // --- 静止或无效运动状态处理 ---
        // 对于 paceList,将静止或无效运动的配速设置为 0
        processedPaceList.add(PaceData(
          timestamp: strideData.timestamp,
          pace: 0,
        ));
      }
    }
  } else if (initialAvgPaceInSm != null && initialAvgPaceInSm > 0) {
    // 如果没有详细步幅数据,则基于平均配速生成模拟数据
    final initialAvgPaceInSk = (initialAvgPaceInSm * 1000).round();
    final totalPoints10s = (validTime / 10).ceil();
    for (int i = 0; i < totalPoints10s; i++) {
      final timestamp = startTime + (i * 10);
      if (timestamp > endTimestamp) break;
      // 添加±10%的随机波动使数据更真实
      final variation = (initialAvgPaceInSk * 0.1).round();
      final randomFactor = (i % 7 - 3) / 3.0; // -1.0 到 1.0 的变化
      final actualPaceSk =
          initialAvgPaceInSk + (variation * randomFactor).round();
      final finalPaceValueSk =
          actualPaceSk > 0 ? actualPaceSk : initialAvgPaceInSk;

      processedPaceList.add(PaceData(
        timestamp: timestamp,
        pace: finalPaceValueSk, // 单位:秒/公里
      ));
    }
  }

  // JSON中的值已是 s/km 单位值
  finalAvgPace = json["avgPace"] ?? json["averageSpeed"];
  finalMaxPace = json["maxPace"] ?? json["maxSpeed"];
  finalMinPace = json["minPace"] ?? json["minSpeed"];

  // 创建 TrainingInfo 实例
  TrainingInfo trainingInfo = TrainingInfo(
    type: json["type"] ?? -1,
    startTime: startTime,
    endTime: endTime,
    validTime: validTime,
    steps: steps,
    distance: distance,
    calories: json["calories"] ?? -1,
    hrList: processedHrList,
    cadences: finalCadencesPerMinute, // 现在是每分钟数据
    strides: finalStridesPerMinute, // 现在是每分钟数据
    cadencesPer10s: finalCadencesPer10s, // 详细的每10秒数据(值的集合)
    stridesPer10s: finalStridesPer10s, // 详细的每10秒数据(值的集合)
    hrInterval: json["hrInterval"] ?? 10, // 默认10秒间隔
    cadencesInterval: 10, // 统一每10秒间隔
    stridesInterval: 10, // 统一每10秒间隔
    cadencesList: processedCadencesList, // 每10秒详细数据(值+时间戳的map集合)
    stridesList: processedStridesList, // 每10秒详细数据(值+时间戳的map集合)
    avgElevation: finalAvgElevation,
    maxElevation: finalMaxElevation,
    minElevation: finalMinElevation,
    avgPace: finalAvgPace,
    maxPace: finalMaxPace,
    minPace: finalMinPace,
    paceList: processedPaceList,
  );

  // 计算心率区间统计
  trainingInfo.hrZone = trainingInfo._calculateHrZone(processedHrList);

  return trainingInfo;
}