diff --git a/lib/common/utils/db_training_helper.dart b/lib/common/utils/db_training_helper.dart index 4ae5d55..d457454 100644 --- a/lib/common/utils/db_training_helper.dart +++ b/lib/common/utils/db_training_helper.dart @@ -64,7 +64,7 @@ class DBTrainingHelper { txn.execute(TrainingDdl.ddlForGroup); txn.execute(TrainingDdl.ddlForPlan); txn.execute(TrainingDdl.ddlForPlanHasGroup); - txn.execute(TrainingDdl.ddlForTrainedLog); + txn.execute(TrainingDdl.ddlForTrainedDetailLog); }); } @@ -710,27 +710,29 @@ class DBTrainingHelper { } ///***********************************************/ - /// training_log 的相关操作 + /// training_detail_log 的相关操作 /// + /// 插入单个计划基本信息 - Future insertTrainingLog(TrainedLog log) async => - (await database).insert(TrainingDdl.tableNameOfTrainedLog, log.toMap()); + Future insertTrainedDetailLog(TrainedDetailLog log) async => + (await database) + .insert(TrainingDdl.tableNameOfTrainedDetailLog, log.toMap()); - Future> insertTrainingLogList( - List tlList, + // 批量插入训练日志(备份恢复时有用到) + Future> insertTrainingDetailLogList( + List tlList, ) async { var batch = (await database).batch(); for (var item in tlList) { - batch.insert(TrainingDdl.tableNameOfTrainedLog, item.toMap()); + batch.insert(TrainingDdl.tableNameOfTrainedDetailLog, item.toMap()); } return batch.commit(); } - // 查询指定训练以及其所有动作 - // 训练支持条件查询,估计训练的数量不会多,就暂时不分页;同事关联的动作就全部带出。 - Future> searchTrainedLogWithGroupBasic({ + // 2023-12-27 日志改为宽表,直接查出数据,不用关联查询 + Future> queryTrainedDetailLog({ int? userId, String? startDate, String? endDate, @@ -757,194 +759,33 @@ class DBTrainingHelper { // 如果有传入创建时间排序,不是传的降序一律升序 var sort = gmtCreateSort?.toLowerCase() == 'desc' ? 'DESC' : 'ASC'; - final userRows = await db.query( - TrainingDdl.tableNameOfTrainedLog, + final rows = await db.query( + TrainingDdl.tableNameOfTrainedDetailLog, where: where.isNotEmpty ? where.join(" AND ") : null, whereArgs: whereArgs.isNotEmpty ? whereArgs : null, orderBy: 'trained_date $sort', ); - // 查询到了基础日志,遍历查询每个详情数据???性能好差 - List logs = - userRows.map((row) => TrainedLog.fromMap(row)).toList(); - - final list = []; - - for (final row in logs) { - /// 理论上,训练日志中group id和plan id不能也不会同时为空 - /// 从训练 和 计划查询到指定天数的训练的过程中,默认全部数据都存在且唯一(很丑陋,别这样写)。 - // 不做空判断,出错在最外面嵌套个try catch - if (row.groupId != null) { - var tempGroup = TrainingGroup.fromMap((await db.query( - TrainingDdl.tableNameOfGroup, - where: 'group_id = ?', - whereArgs: [row.groupId], - )) - .first); - - list.add(TrainedLogWithGroupBasic(log: row, group: tempGroup)); - } else if (row.planId != null) { - var tempPlan = TrainingPlan.fromMap((await db.query( - TrainingDdl.tableNameOfPlan, - where: 'plan_id = ?', - whereArgs: [row.planId])) - .first); - - // 查到plan之后再通过daynumber查询groupId,再查到group - (await db.query(TrainingDdl.tableNameOfPlanHasGroup, - where: 'plan_id = ?', whereArgs: [row.planId])) - .map((e) => PlanHasGroup.fromMap(e)) - .forEach((e) async { - if (e.dayNumber == row.dayNumber) { - /// ???2023-12-04 还真不一定有,计划跟练之后又有修改,导致某些训练、动作都有变化了,那么这里就一定会报错了。。 - /// 暂时有跟练的计划不让修改,后续再看如何设计日志表 - var tempGroup = TrainingGroup.fromMap((await db.query( - TrainingDdl.tableNameOfGroup, - where: 'group_id = ?', - whereArgs: [e.groupId], - )) - .first); - - // 如果有计划但是没有跑到这里来的话,那么数据某个地方就有问题 - list.add(TrainedLogWithGroupBasic( - log: row, group: tempGroup, plan: tempPlan)); - } - }); - } else { - throw Exception("训练编号和计划编号同时为空"); - } - } - - return list; - } - - /* - // 查询指定训练以及其所有动作 - // 训练支持条件查询,估计训练的数量不会多,就暂时不分页;同事关联的动作就全部带出。 - Future> bakSearchTrainedLogWithGroupBasic({ - int? userId, - String? startDate, - String? endDate, - String? gmtCreateSort = "ASC", // 按创建时间升序或者降序排序 - }) async { - final db = await database; - - var where = []; - var whereArgs = []; - - if (userId != null) { - where.add(" user_id = ? "); - whereArgs.add(userId); - } - if (startDate != null) { - where.add(" trained_date >= ? "); - whereArgs.add(startDate); - } - if (endDate != null) { - where.add(" trained_date <= ? "); - whereArgs.add(endDate); - } - - // 如果有传入创建时间排序,不是传的降序一律升序 - var sort = gmtCreateSort?.toLowerCase() == 'desc' ? 'DESC' : 'ASC'; - - final userRows = await db.query( - TrainingDdl.tableNameOfTrainedLog, - where: where.isNotEmpty ? where.join(" AND ") : null, - whereArgs: whereArgs.isNotEmpty ? whereArgs : null, - orderBy: 'trained_date $sort', - ); - - // 查询到了基础日志,遍历查询每个详情数据???性能好差 - List logs = - userRows.map((row) => TrainedLog.fromMap(row)).toList(); - - final list = []; - - for (final row in logs) { - // 默认是这三个值 - var tempLog1, tempPlan1, tempGroup1; - - if (row.groupId != null) { - final groupRows = await db.query( - TrainingDdl.tableNameOfGroup, - where: 'group_id = ?', - whereArgs: [row.groupId], - ); - - // ???理论上这里只有查到1个group,且不应该差不多(暂不考虑异常情况) - if (groupRows.isNotEmpty) { - tempGroup1 = TrainingGroup.fromMap(groupRows[0]); - } - } - if (row.planId != null) { - final planRows = await db.query( - TrainingDdl.tableNameOfPlan, - where: 'plan_id = ?', - whereArgs: [row.planId], - ); - - // ???理论上这里只有查到1个 plan ,且不应该差不多(暂不考虑异常情况) - if (planRows.isNotEmpty) { - TrainingPlan tempPlan = TrainingPlan.fromMap(planRows[0]); - - tempPlan1 = tempPlan; - - // 查到plan之后再通过daynumber查询groupId,再查到group - - final phgRows = await db.query( - TrainingDdl.tableNameOfPlanHasGroup, - where: 'plan_id = ?', - whereArgs: [row.planId], - ); - - if (phgRows.isNotEmpty) { - var tempPhgs = phgRows.map((e) => PlanHasGroup.fromMap(e)).toList(); - - for (var e in tempPhgs) { - if (e.dayNumber == row.dayNumber) { - final groupRows = await db.query( - TrainingDdl.tableNameOfGroup, - where: 'group_id = ?', - whereArgs: [e.groupId], - ); - - if (groupRows.isNotEmpty) { - TrainingGroup group = TrainingGroup.fromMap(groupRows[0]); - tempGroup1 = group; - } - } - } - } - } - } - - var ttt = TrainedLogWithGroupBasic( - group: tempGroup1, plan: tempPlan1, log: tempLog1); - - list.add(ttt); - } - - return list; + return rows.map((row) => TrainedDetailLog.fromMap(row)).toList(); } - */ - /// 查询指定计划中各个训练日上一次训练的时间 - /// 用于点击知道计划进入训练日列表时,显示每个训练日最近一次训练时间的训练记录 - Future> searchLastTrainingLogByPlanId( + /// 2023-12-27 查询训练计划中每一个训练日最近一次跟练的时间 + /// 因为group_name和plan_name是唯一的,而id是自增,可能删除之后万一新的又和旧的编号一样了 + /// 所以通过名称查询;而且这个名称也不会是用户手动输入,所以也不担心匹配不上。 + Future> queryLastTrainingDetailLogByPlanName( TrainingPlan plan, ) async { final db = await database; // 对应计划的每个训练日编号作为key,该训练日的最新训练记录作为value。 - Map logMap = {}; + Map logMap = {}; /// 只找最后一次的记录,那就创建时间倒序查询计划编号和对应训练日编号的最新数据 for (var i = 0; i < plan.planPeriod; i++) { final logRows = await db.query( - TrainingDdl.tableNameOfTrainedLog, - where: "plan_id = ? AND day_number = ? ", - whereArgs: [plan.planId, i + 1], + TrainingDdl.tableNameOfTrainedDetailLog, + where: "plan_name = ? AND day_number = ? ", + whereArgs: [plan.planName, i + 1], orderBy: 'trained_date DESC', ); @@ -952,149 +793,38 @@ class DBTrainingHelper { logMap[i + 1] = null; } else { // 训练时间倒序排列的,所以选第一个即可 - logMap[i + 1] = TrainedLog.fromMap(logRows.first); + logMap[i + 1] = TrainedDetailLog.fromMap(logRows.first); } } return logMap; } - /// - /// 2023-12-14 - /// 判断某个锻炼是否被使用,有被使用则不允许删除 - /// 具体就是这个exercise是否有对应的action, - /// 该action所属的group是否存在于训练日志中; - /// 该action所属的group是否存在于某个计划中,而该计划存在于训练日志中; - - Future isExerciseUsed(int exerciseId) async { - Database db = await database; - - // 1 找到exercise对应的action,如果没有,则是没有被使用 - var actionRows = await db.query( - TrainingDdl.tableNameOfAction, - where: 'exercise_id = ? ', - whereArgs: [exerciseId], - ); - if (actionRows.isEmpty) return false; - - // 2 找到所有的action,并找到对应的group - // 一个exercise可能对应不同group中多个不同的action - for (var row in actionRows) { - var action = TrainingAction.fromMap(row); - // 寻找action所属的group - var groupRows = await db.query( - TrainingDdl.tableNameOfGroup, - where: 'action_id = ? ', - whereArgs: [action.actionId], - ); - // 当前动作没被训练使用则继续找下一个 - if (groupRows.isEmpty) continue; - - // 正常就应该一个action只有一个对应的 group - var group = TrainingGroup.fromMap(groupRows.first); - // 先判断group有没有直接被使用 - var rst = await isGroupUsed(group.groupId!); - // 2-1 如果action对应的group有被使用,就不用后续操作了 - if (rst) return true; - - // 2-2 group没有直接使用,再判断包含该group的plan是否有被使用 - var planRows = await db.query( - TrainingDdl.tableNameOfPlanHasGroup, - where: 'group_id = ? ', - whereArgs: [group.groupId], - ); - - // 2-2-1 group没有对应的plan,则不用后续操作了 - if (planRows.isEmpty) return false; - - // 2-2-2 有对应的plan,检测是否有被用到 - for (var plan in planRows) { - var rst = await isPlanUsed(TrainingPlan.fromMap(plan).planId!); - // 2-2-2-1 只要有一个被用到,就不用继续了 - if (rst) return true; - } - } - - // action、group、plan及其日志都没有用到,就最终也没用到 - return false; - } - - // 指定训练是否已有训练记录 - Future isGroupUsed(int groupId) async { - Database db = await database; - - var groupLogRows = await db.query( - TrainingDdl.tableNameOfTrainedLog, - where: 'group_id = ? ', - whereArgs: [groupId], - ); - - if (groupLogRows.isEmpty) return false; - - return true; - } - - // 指定计划是否已有运动记录 - Future isPlanUsed(int planId) async { - Database db = await database; - - var logRows = await db.query( - TrainingDdl.tableNameOfTrainedLog, - where: 'plan_id = ? ', - whereArgs: [planId], - ); - - if (logRows.isEmpty) return false; - - return true; - } - - /// - /// 直接联合查询判断是否被使用 - /// - Future> isExerciseUsedByRawSQL(int exerciseId) async { + /// 2023-12-27 因为已经有了训练日志宽表,所以,即便有训练日志也可以删除; + /// 因此判断是否被使用排除掉存在于日志表这一条;也就是说: + /// 计划可以随便删; + /// 训练没有被计划使用即可删除; + /// exercise没有对应action(没有被训练使用)即可删除 + Future>> isExerciseUsed(int exerciseId) async { Database db = await database; // 如果对应的exercise编号关联查询的结果不为空,则说明有被使用,不允许删除 - var rows = await db.rawQuery( + return await db.rawQuery( ''' - SELECT a.action_id,g.group_id,phg.plan_id,l.trained_log_id + SELECT a.action_id,g.group_id,phg.plan_id FROM ${TrainingDdl.tableNameOfAction} a LEFT JOIN ${TrainingDdl.tableNameOfGroup} g ON g.group_id = a.group_id LEFT JOIN ${TrainingDdl.tableNameOfPlanHasGroup} phg ON phg.group_id = g.group_id - LEFT JOIN ${TrainingDdl.tableNameOfTrainedLog} l ON l.plan_id = phg.plan_id OR l.group_id = phg.group_id WHERE a.exercise_id = $exerciseId; ''', ); - - return rows.map((e) => ExerciseUsageVO.fromMap(e)).toList(); - } - - Future> isGroupUsedByRawSQL(int groupId) async { - Database db = await database; - - // 这个训练有日志或者训练所属的计划有日志都算,但没法区分group是直接跟练的训练还是计划的某一天 - var rows = await db.rawQuery( - ''' - SELECT phg.plan_id,l.trained_log_id - FROM ${TrainingDdl.tableNameOfPlanHasGroup} phg - LEFT JOIN ${TrainingDdl.tableNameOfTrainedLog} l ON l.plan_id = phg.plan_id OR l.group_id = phg.group_id - WHERE phg.group_id = $groupId; - ''', - ); - - return rows.map((e) => ExerciseUsageVO.fromMap(e)).toList(); } - Future> isPlanUsedByRawSQL(int planId) async { - Database db = await database; - - var logRows = await db.query( - TrainingDdl.tableNameOfTrainedLog, - where: 'plan_id = ? ', - whereArgs: [planId], - ); - - return logRows.map((e) => ExerciseUsageVO.fromMap(e)).toList(); - } + // 这个训练有所属的计划 + Future>> isGroupUsed(int groupId) async => + await (await database).query( + TrainingDdl.tableNameOfPlanHasGroup, + where: 'group_id = ? ', + whereArgs: [groupId], + ); } diff --git a/lib/common/utils/ddl_training.dart b/lib/common/utils/ddl_training.dart index 33b41fa..2719ade 100644 --- a/lib/common/utils/ddl_training.dart +++ b/lib/common/utils/ddl_training.dart @@ -24,8 +24,8 @@ class TrainingDdl { // 训练计划-动作组关系表 static const tableNameOfPlanHasGroup = 'ff_plan_has_group'; - // 训练日志记录表 - static const tableNameOfTrainedLog = 'ff_trained_log'; + // 2023-12-27 训练日志基础宽表,不再级联查询plan和group + static const tableNameOfTrainedDetailLog = 'ff_trained_detail_log'; static const String ddlForExercise = """ CREATE TABLE $tableNameOfExercise ( @@ -101,19 +101,24 @@ class TrainingDdl { ); """; - static const String ddlForTrainedLog = """ - CREATE TABLE $tableNameOfTrainedLog ( - trained_log_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, - trained_date TEXT, - user_id INTEGER NOT NULL, - plan_id INTEGER, - day_number INTEGER, - group_id INTEGER, - trained_start_time TEXT NOT NULL, - trained_end_time TEXT NOT NULL, - trained_duration INTEGER NOT NULL, - totol_paused_time INTEGER NOT NULL, - total_rest_time INTEGER NOT NULL + static const String ddlForTrainedDetailLog = """ + CREATE TABLE $tableNameOfTrainedDetailLog ( + trained_detail_log_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, + trained_date TEXT, + user_id INTEGER NOT NULL, + plan_name TEXT, + plan_category TEXT, + plan_level TEXT, + day_number INTEGER, + group_name TEXT, + group_category TEXT, + group_level TEXT, + consumption INTEGER, + trained_start_time TEXT NOT NULL, + trained_end_time TEXT NOT NULL, + trained_duration INTEGER NOT NULL, + totol_paused_time INTEGER NOT NULL, + total_rest_time INTEGER NOT NULL ); """; } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index dadcbf7..e27caf8 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -102,7 +102,7 @@ "exercise": "动作", "exerciseLabel": "基础动作", "exerciseSubtitle": "管理运动的各个基础动作", - "exerciseDeleteAlert":"确认要删除该动作: {exerciseName}?删除后不可恢复!", + "exerciseDeleteAlert":"确认要删除该动作: {exerciseName} ? 删除后不可恢复!", "exerciseInUse":"该动作 {exerciseName} 有被训练或计划使用,暂不支持删除.", "exerciseQuerys": "{num, select, 0{训练部位} 1{代号} 2{名称} 3{级别} 4{类型} 5{分类} 6{器械} 7{计量} other{其他} }", @@ -132,8 +132,8 @@ "@workoutQuerys": { "description": "常用workout查询用的标签" }, - "groupDeleteAlert":"确认要删除该训练动作组:{workoutName}?删除后不可恢复!", - "groupInUse":"该训练 {workoutName} 有被计划使用或者存在跟练记录,暂不支持删除", + "groupDeleteAlert":"确认要删除该训练动作组: {workoutName} ? 删除后不可恢复!", + "groupInUse":"该训练 {workoutName} 有被计划使用,暂不支持删除", "modifyGroupLabels": "{num, select, 0{修改训练} 1{新建训练} 2{主要肌肉} 3{次要肌肉} 4{技术要点} 5{语音提示要点} 6{动作图片} 7{用户上传} other{其他} }", "@modifyGroupLabels": { @@ -181,7 +181,7 @@ "@planQuerys": { "description": "常用 plan 查询用的标签" }, - "planDeleteAlert":"确认要删除该训练计划:{planName}?删除后不可恢复!", + "planDeleteAlert":"确认要删除该训练计划: {planName} ? 删除后不可恢复!", "planInUse":"该训练计划 {planName} 存在跟练记录,暂不支持删除", "modifyPlanLabels": "{num, select, 0{修改计划} 1{新建计划} 2{名称} 3{代号} 4{分类} 5{级别} 6{训练周期} 7{概述} other{其他} }", diff --git a/lib/models/training_state.dart b/lib/models/training_state.dart index 729f9cf..3509ee7 100644 --- a/lib/models/training_state.dart +++ b/lib/models/training_state.dart @@ -304,20 +304,27 @@ class PlanHasGroup { } } -// 训练日志表 -class TrainedLog { - int? trainedLogId; // 自增的,可以不传 - int? planId, dayNumber, groupId; +// 2023-12-27 训练日志宽表,不再级联查询plan和group,这样后两者有删除也可以查看历史记录 +class TrainedDetailLog { + int? trainedDetailLogId; // 自增的,可以不传 + int? dayNumber, consumption; + String? planName, planCategory, planLevel; + String? groupName, groupCategory, groupLevel; String trainedDate, trainedStartTime, trainedEndTime; int userId, trainedDuration, totolPausedTime, totalRestTime; - TrainedLog({ - this.trainedLogId, + TrainedDetailLog({ + this.trainedDetailLogId, required this.trainedDate, required this.userId, - this.planId, + this.planName, + this.planCategory, + this.planLevel, this.dayNumber, - this.groupId, + this.groupName, + this.groupCategory, + this.groupLevel, + this.consumption, required this.trainedStartTime, required this.trainedEndTime, required this.trainedDuration, @@ -328,12 +335,17 @@ class TrainedLog { // 转换成一个Map。键必须对应于数据库中的列名。 Map toMap() { return { - 'trained_log_id': trainedLogId, + 'trained_detail_log_id': trainedDetailLogId, 'trained_date': trainedDate, 'user_id': userId, - 'plan_id': planId, + 'plan_name': planName, + 'plan_category': planCategory, + 'plan_level': planLevel, 'day_number': dayNumber, - 'group_id': groupId, + 'group_name': groupName, + 'group_category': groupCategory, + 'group_level': groupLevel, + 'consumption': consumption, 'trained_start_time': trainedStartTime, 'trained_end_time': trainedEndTime, 'trained_duration': trainedDuration, @@ -343,14 +355,19 @@ class TrainedLog { } // 用于从数据库行映射到 ServingInfo 对象的 fromMap 方法 - factory TrainedLog.fromMap(Map map) { - return TrainedLog( - trainedLogId: map['trained_log_id'] as int?, + factory TrainedDetailLog.fromMap(Map map) { + return TrainedDetailLog( + trainedDetailLogId: map['trained_detail_log_id'] as int?, trainedDate: map['trained_date'] as String, userId: map['user_id'] as int, - planId: map['plan_id'] as int?, + planName: map['plan_name'] as String?, + planCategory: map['plan_category'] as String?, + planLevel: map['plan_level'] as String?, dayNumber: map['day_number'] as int?, - groupId: map['group_id'] as int?, + groupName: map['group_name'] as String?, + groupCategory: map['group_category'] as String?, + groupLevel: map['group_level'] as String?, + consumption: map['consumption'] as int?, trainedStartTime: map['trained_start_time'] as String, trainedEndTime: map['trained_end_time'] as String, trainedDuration: map['trained_duration'] as int, @@ -363,9 +380,10 @@ class TrainedLog { @override String toString() { return ''' - TrainedLog{ - trainedLogId:$trainedLogId, trainedDate:$trainedDate, userId:$userId, - planId:$planId, dayNumber:$dayNumber, groupId:$groupId, + TrainedDetailLog{ + trainedDetailLogId:$trainedDetailLogId, trainedDate:$trainedDate, userId:$userId, + planName:$planName, planCategory:$planCategory, planLevel:$planLevel, dayNumber:$dayNumber, + groupName:$groupName, groupCategory:$groupCategory, groupLevel:$groupLevel, consumption:$consumption, trainedStartTime:$trainedStartTime, trainedEndTime:$trainedEndTime, trainedDuration:$trainedDuration, totolPausedTime:$totolPausedTime, totalRestTime:$totalRestTime } @@ -440,63 +458,3 @@ class ActionPractice { '''; } } - -// 训练日志要带上训练或者计划的基础信息(没有动作的信息) -class TrainedLogWithGroupBasic { - final TrainedLog log; - // 如果直接的训练的跟练,对应训练的数据;如果是计划的某一天的训练,同样记录训练信息,是哪一天在log表中 - final TrainingGroup? group; - // 如果是某个计划的某一天的训练,带上计划的信息 - final TrainingPlan? plan; - - TrainedLogWithGroupBasic({ - required this.log, - // 不查询训练是也可以用这个类,代替日志类? - this.group, - this.plan, - }); - - @override - String toString() { - return ''' - TrainedLogWithGroupBasic { - log: $log, group: $group, plan: $plan - '''; - } -} - -// 2023-12-14 如果一个动作有被使用,则记录被哪个关联表使用 -class ExerciseUsageVO { - int? actionId; - int? groupId; - int? planId; - int? logId; - - ExerciseUsageVO({ - this.actionId, - this.groupId, - this.planId, - this.logId, - }); - - // 作为vo,应该只有查询的对应。目前只有联合查询时用到 - // 注意栏位前缀有不同表的同名栏位,栏位会重复,而不会在栏位前带上表名 - factory ExerciseUsageVO.fromMap(Map map) { - return ExerciseUsageVO( - actionId: map['action_id'] as int?, - groupId: map['group_id'] as int?, - planId: map['plan_id'] as int?, - logId: map['trained_log_id'] as int?, - ); - } - - @override - String toString() { - return 'ExerciseUsageVO {actionId: $actionId, groupId: $groupId, planId: $planId, logId: $logId }'; - } - - // @override - // String toString() { - // return '配置编号:$actionId,训练编号:$groupId,计划编号:$planId,日志:$logId'; - // } -} diff --git a/lib/views/me/_feature_mock_data/index.dart b/lib/views/me/_feature_mock_data/index.dart index 2e01da1..fb324f7 100644 --- a/lib/views/me/_feature_mock_data/index.dart +++ b/lib/views/me/_feature_mock_data/index.dart @@ -157,15 +157,6 @@ class _FeatureMockDemoState extends State { child: const Text("userId为1的新增10条随机BMI数据"), ), - TextButton( - onPressed: () async { - await insertTrainingLogDemo(); - if (!mounted) return; - _showSimpleDialog(context, "已新增2条【训练日志】示例"); - }, - child: const Text("插入两条训练日志(先新增基础数据)"), - ), - ElevatedButton( onPressed: () async { await _dietaryHelper.deleteDB(); @@ -186,7 +177,8 @@ class _FeatureMockDemoState extends State { } await insertExtraUsers(); await insertBMIDemo(); - await insertTrainingLogDemo(); + + await insertTrainingDetailLogDemo(); if (!mounted) return; _showSimpleDialog(context, "已插入所有数据"); diff --git a/lib/views/me/_feature_mock_data/test_funcs.dart b/lib/views/me/_feature_mock_data/test_funcs.dart index 2be0e3f..f107fe8 100644 --- a/lib/views/me/_feature_mock_data/test_funcs.dart +++ b/lib/views/me/_feature_mock_data/test_funcs.dart @@ -358,17 +358,22 @@ Future insertOneRandomPlanHasGroup() async { return planId; } -// 插入一条跟练记录(不那么随机,且需要已有训练的基础数据) -insertTrainingLogDemo({int? size = 10}) async { - print("【【【 插入测试数据 start-->:insertTrainingLogDemo "); +// 2023-12-27 插入训练日志宽表数据,展示运动报告时不需要级联查询基础表 +insertTrainingDetailLogDemo({int? size = 10}) async { + print("【【【 插入测试数据 start-->:insertTrainingDetailLogDemo "); + + /// 2023-12-27 正好是测试日志宽表,不会关联其他基础表,数据随意写就好了 // 计划中的某一天 - var tl1 = TrainedLog( + var tl1 = TrainedDetailLog( trainedDate: getCurrentDateTime(), userId: Random().nextInt(3) + 1, // 单次记录,有计划及其训练日,就没有训练编号了;反之亦然 - planId: 1, - dayNumber: 2, + planName: generateRandomString(5, 20), + planCategory: + categoryOptions[Random().nextInt(categoryOptions.length)].value, + planLevel: levelOptions[Random().nextInt(levelOptions.length)].value, + dayNumber: Random().nextInt(8) + 1, // 起止时间就测试插入时的1个小时 trainedStartTime: DateFormat(constDatetimeFormat) .format(DateTime.now().add(const Duration(hours: -1))), @@ -379,14 +384,19 @@ insertTrainingLogDemo({int? size = 10}) async { totalRestTime: 12 * 60, // 休息的总时间 ); - var logId1 = await _trainingHelper.insertTrainingLog(tl1); + var logId1 = await _trainingHelper.insertTrainedDetailLog(tl1); // 直接的某个训练 - var tl2 = TrainedLog( + var tl2 = TrainedDetailLog( trainedDate: getCurrentDateTime(), userId: Random().nextInt(3) + 1, // 单次记录,有计划及其训练日,就没有训练编号了;反之亦然 - groupId: 1, + groupName: generateRandomString(5, 20), + groupCategory: + groupCategoryOptions[Random().nextInt(groupCategoryOptions.length)] + .value, + groupLevel: levelOptions[Random().nextInt(levelOptions.length)].value, + consumption: Random().nextInt(1000), // 起止时间就测试插入时的1个小时 trainedStartTime: DateFormat(constDatetimeFormat) .format(DateTime.now().add(const Duration(hours: -2))), @@ -398,16 +408,19 @@ insertTrainingLogDemo({int? size = 10}) async { totalRestTime: 20 * 60, // 休息的总时间 ); - var logId2 = await _trainingHelper.insertTrainingLog(tl2); + var logId2 = await _trainingHelper.insertTrainedDetailLog(tl2); // 前一天的日志 计划中的某一天 - var tl3 = TrainedLog( + var tl3 = TrainedDetailLog( trainedDate: DateFormat(constDatetimeFormat) .format(DateTime.now().add(const Duration(days: -1))), userId: Random().nextInt(3) + 1, // 单次记录,有计划及其训练日,就没有训练编号了;反之亦然 - planId: 1, - dayNumber: 1, + planName: generateRandomString(5, 20), + planCategory: + categoryOptions[Random().nextInt(categoryOptions.length)].value, + planLevel: levelOptions[Random().nextInt(levelOptions.length)].value, + dayNumber: Random().nextInt(8) + 1, // 起止时间就测试插入时的1个小时 trainedStartTime: DateFormat(constDatetimeFormat) .format(DateTime.now().add(const Duration(days: -1, hours: -1))), @@ -419,10 +432,10 @@ insertTrainingLogDemo({int? size = 10}) async { totalRestTime: 12 * 60, // 休息的总时间 ); - var logId3 = await _trainingHelper.insertTrainingLog(tl3); + var logId3 = await _trainingHelper.insertTrainedDetailLog(tl3); print( - "【【【 插入测试数据 end-->:insertTrainingLogDemo logId: $logId1 $logId2 $logId3", + "【【【 插入测试数据 end-->:insertTrainingDetailLogDemo logId: $logId1 $logId2 $logId3", ); } diff --git a/lib/views/me/backup_and_restore/index.dart b/lib/views/me/backup_and_restore/index.dart index e3ad205..373bc74 100644 --- a/lib/views/me/backup_and_restore/index.dart +++ b/lib/views/me/backup_and_restore/index.dart @@ -319,9 +319,9 @@ class _BackupAndRestoreState extends State { } else if (filename == "ff_meal_photo.json") { var temp = jsonMapList.map((e) => MealPhoto.fromMap(e)).toList(); await _dietaryHelper.insertMealPhotoList(temp); - } else if (filename == "ff_trained_log.json") { - var temp = jsonMapList.map((e) => TrainedLog.fromMap(e)).toList(); - await _trainingHelper.insertTrainingLogList(temp); + } else if (filename == "ff_trained_detail_log.json") { + var temp = jsonMapList.map((e) => TrainedDetailLog.fromMap(e)).toList(); + await _trainingHelper.insertTrainingDetailLogList(temp); } else if (filename == "ff_diary.json") { var temp = jsonMapList.map((e) => Diary.fromMap(e)).toList(); await _diaryHelper.insertDiaryList(temp); diff --git a/lib/views/training/exercise/index.dart b/lib/views/training/exercise/index.dart index 57a6881..5265abe 100644 --- a/lib/views/training/exercise/index.dart +++ b/lib/views/training/exercise/index.dart @@ -278,8 +278,7 @@ class _TrainingExerciseState extends State { // 左滑显示删除确认弹窗,???删除时还要检查删除者是否为创建者,这里只是测试左滑删除卡片 confirmDismiss: (DismissDirection direction) async { // 如果该基础活动有被使用,则不允许直接删除 - var list = - await _dbHelper.isExerciseUsedByRawSQL(exerciseItem.exerciseId!); + var list = await _dbHelper.isExerciseUsed(exerciseItem.exerciseId!); if (!mounted) return false; if (list.isNotEmpty) { diff --git a/lib/views/training/plans/group_list.dart b/lib/views/training/plans/group_list.dart index 5a1b718..e84d073 100644 --- a/lib/views/training/plans/group_list.dart +++ b/lib/views/training/plans/group_list.dart @@ -41,7 +41,7 @@ class _GroupListState extends State { List groupList = []; // 当前计划的每个训练日最后一次训练的日志map - Map logMap = {}; + Map logMap = {}; // 是否在加载数据 bool isLoading = false; @@ -73,7 +73,7 @@ class _GroupListState extends State { ); // 查询该训练计划的跟练日志信息,用于显示每个训练的最后一次跟练时间 - var tempLog = await _dbHelper.searchLastTrainingLogByPlanId( + var tempLog = await _dbHelper.queryLastTrainingDetailLogByPlanName( widget.planItem, ); @@ -352,7 +352,7 @@ class _GroupListState extends State { MaterialPageRoute( builder: (context) => ActionList( groupItem: groupItem, - planId: widget.planItem.planId, + planItem: widget.planItem, dayNumber: index + 1, ), ), diff --git a/lib/views/training/plans/index.dart b/lib/views/training/plans/index.dart index 56559d5..06d0d01 100644 --- a/lib/views/training/plans/index.dart +++ b/lib/views/training/plans/index.dart @@ -301,59 +301,46 @@ class _TrainingPlansState extends State { }, // 长按点击弹窗提示是否删除 onLongPress: () async { - // 如果该基础活动有被使用,则不允许直接删除 - var list = - await _dbHelper.isPlanUsedByRawSQL(planItem.plan.planId!); - - if (!mounted) return; - if (list.isNotEmpty) { - commonExceptionDialog( - context, - CusAL.of(context).exceptionWarningTitle, - CusAL.of(context).planInUse(planItem.plan.planName), - ); - } else { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text(CusAL.of(context).deleteConfirm), - content: Text(CusAL.of(context) - .planDeleteAlert(planItem.plan.planName)), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context, false); - }, - child: Text(CusAL.of(context).cancelLabel), - ), - TextButton( - onPressed: () { - Navigator.pop(context, true); - }, - child: Text(CusAL.of(context).confirmLabel), - ), - ], + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text(CusAL.of(context).deleteConfirm), + content: Text(CusAL.of(context) + .planDeleteAlert(planItem.plan.planName)), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context, false); + }, + child: Text(CusAL.of(context).cancelLabel), + ), + TextButton( + onPressed: () { + Navigator.pop(context, true); + }, + child: Text(CusAL.of(context).confirmLabel), + ), + ], + ); + }, + ).then((value) async { + if (value != null && value) { + try { + await _dbHelper.deletePlanById(planItem.plan.planId!); + + // 删除后重新查询 + getPlanList(); + } catch (e) { + if (!mounted) return; + commonExceptionDialog( + context, + CusAL.of(context).exceptionWarningTitle, + e.toString(), ); - }, - ).then((value) async { - if (value != null && value) { - try { - await _dbHelper.deletePlanById(planItem.plan.planId!); - - // 删除后重新查询 - getPlanList(); - } catch (e) { - if (!mounted) return; - commonExceptionDialog( - context, - CusAL.of(context).exceptionWarningTitle, - e.toString(), - ); - } } - }); - } + } + }); }, ), ], diff --git a/lib/views/training/reports/export/report_pdf_export.dart b/lib/views/training/reports/export/report_pdf_export.dart index aa28022..2e5f16e 100644 --- a/lib/views/training/reports/export/report_pdf_export.dart +++ b/lib/views/training/reports/export/report_pdf_export.dart @@ -26,7 +26,7 @@ Map _pdfLabelMap = { "number": CusLabel(enLabel: 'Page', cnLabel: "第", value: null), "page": CusLabel(enLabel: '', cnLabel: "页", value: null), "name": CusLabel(enLabel: 'Name', cnLabel: "训练名称", value: null), - "dayNumber": CusLabel(enLabel: 'Day ', cnLabel: "训练日 ", value: null), + "dayNumber": CusLabel(enLabel: 'Day', cnLabel: "训练日", value: null), "startAndEnd": CusLabel(enLabel: 'Start & End Time', cnLabel: "训练起止时间", value: null), "trainedDuration": CusLabel( @@ -44,7 +44,7 @@ String _showLabel(String lang, CusLabel cusLable) { } Future makeTrainedReportPdf( - List list, + List list, String startDate, String endDate, { String lang = "cn", @@ -68,11 +68,11 @@ Future makeTrainedReportPdf( ); // 1 先把条目按天分类,每天的所有餐次放到一致pdf中 - Map> logGroupedByDate = {}; + Map> logGroupedByDate = {}; for (var log in list) { // 日志的日期(不含时间) // 训练记录的训练日志存入的是完整的datetime,这里只取date部分 - var tempDate = log.log.trainedDate.split(" ")[0]; + var tempDate = log.trainedDate.split(" ")[0]; if (logGroupedByDate.containsKey(tempDate)) { logGroupedByDate[tempDate]!.add(log); @@ -95,7 +95,7 @@ Future makeTrainedReportPdf( // 构建pdf的页面 _buildPdfPage( - List logData, + List logData, String date, String startDate, String endDate, @@ -192,25 +192,23 @@ _buildHeaderTable(String lang) { } // 构建每餐的子表格数据部分 -_buildBodyTable(List trainedData, String lang) { +_buildBodyTable(List trainedData, String lang) { // 计算所有训练日志的累加时间 int totalRest = - trainedData.fold(0, (prev, item) => prev + item.log.totalRestTime); + trainedData.fold(0, (prev, item) => prev + item.totalRestTime); int totolPaused = - trainedData.fold(0, (prev, item) => prev + item.log.totolPausedTime); + trainedData.fold(0, (prev, item) => prev + item.totolPausedTime); int totalTrained = - trainedData.fold(0, (prev, item) => prev + item.log.trainedDuration); + trainedData.fold(0, (prev, item) => prev + item.trainedDuration); return pw.Table( // 字数据可以不显示边框,更方便看? border: pw.TableBorder.all(color: PdfColors.black), children: [ ...trainedData.map((e) { - var log = e.log; - - var name = (e.plan != null) - ? "${e.plan!.planName} ${_showLabel(lang, _pdfLabelMap['dayNumber']!)} ${log.dayNumber} \n${e.group?.groupName}" - : e.group?.groupName ?? ""; + var name = (e.planName != null) + ? "${e.planName} ${_showLabel(lang, _pdfLabelMap['dayNumber']!)} ${e.dayNumber}" + : e.groupName ?? ""; return pw.TableRow( // 行中数据垂直居中 @@ -224,19 +222,19 @@ _buildBodyTable(List trainedData, String lang) { ), ), expandedSubText( - "${log.trainedStartTime.split(" ")[1]} - ${log.trainedEndTime.split(" ")[1]}", + "${e.trainedStartTime.split(" ")[1]} - ${e.trainedEndTime.split(" ")[1]}", flex: 2, ), expandedSubText( - cusDoubleTryToIntString(log.trainedDuration / 60), + cusDoubleTryToIntString(e.trainedDuration / 60), flex: 2, ), expandedSubText( - cusDoubleTryToIntString(log.totalRestTime / 60), + cusDoubleTryToIntString(e.totalRestTime / 60), flex: 2, ), expandedSubText( - cusDoubleTryToIntString(log.totolPausedTime / 60), + cusDoubleTryToIntString(e.totolPausedTime / 60), flex: 2, ), ], diff --git a/lib/views/training/reports/export/report_pdf_viewer.dart b/lib/views/training/reports/export/report_pdf_viewer.dart index 9e56bc0..d959dd8 100644 --- a/lib/views/training/reports/export/report_pdf_viewer.dart +++ b/lib/views/training/reports/export/report_pdf_viewer.dart @@ -33,7 +33,7 @@ class _TrainedReportPdfViewerState extends State { final DBTrainingHelper _trainingHelper = DBTrainingHelper(); /// 根据条件查询的日记条目数据(所有数据的来源,格式化成VO可以在指定函数中去做) - List tlwgbList = []; + List tdlList = []; bool isLoading = false; @@ -41,11 +41,11 @@ class _TrainedReportPdfViewerState extends State { void initState() { super.initState(); - _queryTrainedLogList(); + _queryTrainedDetailLogList(); } /// 有指定日期查询指定日期的饮食记录条目,没有就当前日期 - _queryTrainedLogList() async { + _queryTrainedDetailLogList() async { if (isLoading) return; setState(() { @@ -53,7 +53,7 @@ class _TrainedReportPdfViewerState extends State { }); // 理论上是默认查询当日的,有选择其他日期则查询指定日期???还要是登录者这个用户编号的 - var temp = await _trainingHelper.searchTrainedLogWithGroupBasic( + var temp = await _trainingHelper.queryTrainedDetailLog( userId: CacheUser.userId, startDate: widget.startDate, endDate: widget.endDate, @@ -61,7 +61,7 @@ class _TrainedReportPdfViewerState extends State { ); setState(() { - tlwgbList = temp; + tdlList = temp; isLoading = false; }); } @@ -77,7 +77,7 @@ class _TrainedReportPdfViewerState extends State { : PdfPreview( initialPageFormat: PdfPageFormat.a4, build: (context) => makeTrainedReportPdf( - tlwgbList, + tdlList, // 在pdf页首会显示查询数据的日期 widget.startDate.split(" ")[0], widget.endDate.split(" ")[0], diff --git a/lib/views/training/reports/index.dart b/lib/views/training/reports/index.dart index e3853c4..71628cc 100644 --- a/lib/views/training/reports/index.dart +++ b/lib/views/training/reports/index.dart @@ -41,7 +41,7 @@ class _TrainingReportsState extends State { final DBTrainingHelper _trainingHelper = DBTrainingHelper(); // 被选中的事件 - late ValueNotifier> _selectedEvents; + late ValueNotifier> _selectedEvents; // 用于展示的日历格式(默认是当前这一个星期,可以切换为最近两个星期、当月) CalendarFormat _calendarFormat = CalendarFormat.week; // 点击两个日期变为选定日期范围 @@ -55,7 +55,7 @@ class _TrainingReportsState extends State { DateTime? _rangeEnd; // 初始化或查询时加载数据,没加载完就都是加载中 - late List trainedLogList; + late List tdlList; // 导出数据时默认选中为最近7天 CusLabel exportDateValue = exportDateList.first; @@ -84,13 +84,13 @@ class _TrainingReportsState extends State { isLoading = true; }); - var list = await _trainingHelper.searchTrainedLogWithGroupBasic( + var list = await _trainingHelper.queryTrainedDetailLog( userId: CacheUser.userId, gmtCreateSort: "DESC", ); setState(() { - trainedLogList = list; + tdlList = list; // 初始化时设定当前选中的日期就是聚焦的日期 _selectedDay = _focusedDay; // 获取当前日期的事件 @@ -101,11 +101,11 @@ class _TrainingReportsState extends State { } // 获取指定某一天的手记列表 - List _getLogsForADay(day) { + List _getLogsForADay(day) { // 训练记录的训练日志存入的是完整的datetime,这里只取date部分 - return trainedLogList + return tdlList .where((e) => - e.log.trainedDate.split(" ")[0] == + e.trainedDate.split(" ")[0] == DateFormat(constDateFormat).format(day)) .toList(); } @@ -267,14 +267,14 @@ class _TrainingReportsState extends State { buildReportsView() { // 统计的是所有的运动次数和总的运动时间 return FutureBuilder( - future: _trainingHelper.searchTrainedLogWithGroupBasic( + future: _trainingHelper.queryTrainedDetailLog( userId: CacheUser.userId, gmtCreateSort: "DESC", ), builder: (BuildContext context, - AsyncSnapshot> snapshot) { + AsyncSnapshot> snapshot) { if (snapshot.hasData) { - List data = snapshot.data!; + List data = snapshot.data!; if (data.isEmpty) { return Center( @@ -288,14 +288,14 @@ class _TrainingReportsState extends State { ); } - // TrainedLogWithGroupBasic-->tlwgb + // TrainedDetailLog-->tlwgb // 计算所有训练日志的累加时间 - int totalRest = data.fold( - 0, (prevVal, tlwgb) => prevVal + tlwgb.log.totalRestTime); - int totolPaused = data.fold( - 0, (prevVal, tlwgb) => prevVal + tlwgb.log.totolPausedTime); - int totalTrained = data.fold( - 0, (prevVal, tlwgb) => prevVal + tlwgb.log.trainedDuration); + int totalRest = + data.fold(0, (prevVal, tdl) => prevVal + tdl.totalRestTime); + int totolPaused = + data.fold(0, (prevVal, tdl) => prevVal + tdl.totolPausedTime); + int totalTrained = + data.fold(0, (prevVal, tdl) => prevVal + tdl.trainedDuration); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -420,7 +420,7 @@ class _TrainingReportsState extends State { Expanded( flex: 3, child: Text( - data.first.log.trainedDate, + data.first.trainedDate, style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.green, @@ -446,9 +446,9 @@ class _TrainingReportsState extends State { Expanded( flex: 3, child: Text( - (data.first.plan != null) - ? "${data.first.plan?.planName} - ${CusAL.of(context).dayNumber(data.first.log.dayNumber ?? 0)}" - : data.first.group?.groupName ?? "", + (data.first.planName != null) + ? "${data.first.planName} - ${CusAL.of(context).dayNumber(data.first.dayNumber ?? 0)}" + : data.first.groupName ?? "", maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( @@ -476,7 +476,7 @@ class _TrainingReportsState extends State { Expanded( flex: 3, child: Text( - "${cusDoubleTryToIntString(data.first.log.trainedDuration / 60)} ${CusAL.of(context).unitLabels('8')}", + "${cusDoubleTryToIntString(data.first.trainedDuration / 60)} ${CusAL.of(context).unitLabels('8')}", style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.green, @@ -574,7 +574,7 @@ class _TrainingReportsState extends State { ), SizedBox(height: 8.sp), // 日历某些操作改变后,显示对应的手记内容列表 - ValueListenableBuilder>( + ValueListenableBuilder>( valueListenable: _selectedEvents, // 当_selectedEvents有变化时,这个builder才会被调用 builder: (context, value, _) { @@ -592,7 +592,7 @@ class _TrainingReportsState extends State { EdgeInsets.symmetric(horizontal: 12.sp, vertical: 4.sp), child: Padding( padding: EdgeInsets.all(10.sp), - child: _buildTrainedLogListTile(log), + child: _buildTrainedDetailLogListTile(log), ), ); }, @@ -610,16 +610,16 @@ class _TrainingReportsState extends State { var [start, end] = getStartEndDateString(30); return FutureBuilder( - future: _trainingHelper.searchTrainedLogWithGroupBasic( + future: _trainingHelper.queryTrainedDetailLog( userId: CacheUser.userId, startDate: start, endDate: end, gmtCreateSort: "DESC", ), builder: (BuildContext context, - AsyncSnapshot> snapshot) { + AsyncSnapshot> snapshot) { if (snapshot.hasData) { - List data = snapshot.data!; + List data = snapshot.data!; if (data.isEmpty) { return Center( @@ -634,10 +634,10 @@ class _TrainingReportsState extends State { } // 将最近30天的记录,按天分组并排序展示。 - Map> logGroupedByDate = {}; + Map> logGroupedByDate = {}; for (var log in data) { // 日志的日期(不含时间) - var temp = log.log.trainedDate.split(" ")[0]; + var temp = log.trainedDate.split(" ")[0]; if (logGroupedByDate.containsKey(temp)) { logGroupedByDate[temp]!.add(log); } else { @@ -679,7 +679,7 @@ class _TrainingReportsState extends State { children: [ if (index != 0) Divider(height: 5.sp, thickness: 3.sp), - _buildTrainedLogListTile(log), + _buildTrainedDetailLogListTile(log), ], ); }, @@ -719,7 +719,7 @@ class _TrainingReportsState extends State { } // 日历表格和最近30天记录的tab都可复用 - _buildTrainedLogListTile(TrainedLogWithGroupBasic log) { + _buildTrainedDetailLogListTile(TrainedDetailLog log) { return ListTile( title: _buildWorkoutNameText(log), subtitle: Column( @@ -728,27 +728,27 @@ class _TrainingReportsState extends State { SizedBox(height: 10.sp), _buildTileRow( CusAL.of(context).trainedCalendarLabels('5'), - '${log.log.trainedStartTime.split(" ")[1]} ~ ${log.log.trainedEndTime.split(" ")[1]}', + '${log.trainedStartTime.split(" ")[1]} ~ ${log.trainedEndTime.split(" ")[1]}', ), _buildTileRow( CusAL.of(context).trainedCalendarLabels('2'), - formatSeconds(log.log.trainedDuration.toDouble()), + formatSeconds(log.trainedDuration.toDouble()), ), _buildTileRow( CusAL.of(context).trainedCalendarLabels('3'), - formatSeconds(log.log.totolPausedTime.toDouble()), + formatSeconds(log.totolPausedTime.toDouble()), ), _buildTileRow( CusAL.of(context).trainedCalendarLabels('4'), - formatSeconds(log.log.totalRestTime.toDouble()), + formatSeconds(log.totalRestTime.toDouble()), ), ], ), ); } - _buildWorkoutNameText(TrainedLogWithGroupBasic log) { - var planName = log.plan?.planName; + _buildWorkoutNameText(TrainedDetailLog log) { + var planName = log.planName; return planName != null ? RichText( textAlign: TextAlign.left, @@ -765,26 +765,19 @@ class _TrainingReportsState extends State { ), ), TextSpan( - text: " $planName \n", + text: " $planName ", style: TextStyle( fontSize: CusFontSizes.itemTitle, color: Colors.green, ), ), TextSpan( - text: CusAL.of(context).dayNumber(log.log.dayNumber ?? 0), + text: CusAL.of(context).dayNumber(log.dayNumber ?? 0), style: TextStyle( color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold, ), ), - TextSpan( - text: ' ${log.group?.groupName}', - style: TextStyle( - fontSize: CusFontSizes.itemTitle, - color: Colors.green, - ), - ), ], ), ) @@ -803,7 +796,7 @@ class _TrainingReportsState extends State { ), ), TextSpan( - text: ' ${log.group?.groupName}', + text: ' ${log.groupName}', style: TextStyle( fontSize: CusFontSizes.itemTitle, color: Colors.green[500], diff --git a/lib/views/training/workouts/action_follow_practice.dart b/lib/views/training/workouts/action_follow_practice.dart index fbb2f51..34990a2 100644 --- a/lib/views/training/workouts/action_follow_practice.dart +++ b/lib/views/training/workouts/action_follow_practice.dart @@ -23,17 +23,17 @@ import '../reports/index.dart'; class ActionFollowPracticeWithTTS extends StatefulWidget { /// 跟练的时候,可能是直接的某个训练;也可能是某个计划的某个训练日。理论上不会两者都有 - final int? planId; + final TrainingPlan? plan; final int? dayNumber; - final int? groupId; + final TrainingGroup? group; // 跟练需要传入动作组数据 final List actionList; const ActionFollowPracticeWithTTS({ Key? key, required this.actionList, - this.groupId, - this.planId, + this.group, + this.plan, this.dayNumber, }) : super(key: key); @@ -58,10 +58,10 @@ class _ActionFollowPracticeWithTTSState /// 跟练的时候,可能是直接的某个训练;也可能是某个计划的某个训练日 /// 理论上不会两者都有 - // 当前的训练编号 - int? groupId; - // 当前的计划编号 - int? planId; + // 当前的训练 + TrainingGroup? group; + // 当前的计划 + TrainingPlan? plan; // 计划编号中的训练日 int? dayNumber; // 当前的动作列表 @@ -150,9 +150,9 @@ class _ActionFollowPracticeWithTTSState setState(() { // 一定要传动作组数据 actions = widget.actionList; - planId = widget.planId; + plan = widget.plan; dayNumber = widget.dayNumber; - groupId = widget.groupId; + group = widget.group; // 进入此跟练页面自动开始 startedMoment = DateTime.now(); }); @@ -1215,13 +1215,20 @@ class _ActionFollowPracticeWithTTSState .toStringAsFixed(0); // 训练日志 - var tempLog = TrainedLog( + var tempDetailLog = TrainedDetailLog( trainedDate: getCurrentDateTime(), userId: CacheUser.userId, - // 单次记录,有计划及其训练日,就没有训练编号了;反之亦然 - planId: planId, + // 单次记录,有计划及其训练日,就没有训练相关栏位了;反之亦然 + planName: plan?.planName, + planCategory: plan?.planCategory, + planLevel: plan?.planLevel, + // 因为这里如果需要plan指定训练日的名称,还需要关联查询planHasGroup,再得到group; + // 此外也会和有planName无groupName的原则冲突,所以这里只记录计划的名称和训练日天数即可 dayNumber: dayNumber, - groupId: groupId, + groupName: group?.groupName, + groupCategory: group?.groupCategory, + groupLevel: group?.groupLevel, + consumption: group?.consumption, // 起止时间都是datetime格式化后的字符串 trainedStartTime: formatDateToString( startedMoment, @@ -1239,7 +1246,7 @@ class _ActionFollowPracticeWithTTSState try { // 插入训练日志 - await _trainingHelper.insertTrainingLog(tempLog); + await _trainingHelper.insertTrainedDetailLog(tempDetailLog); // 跟练结束后就可以停止禁止熄屏 WakelockPlus.disable(); diff --git a/lib/views/training/workouts/action_list.dart b/lib/views/training/workouts/action_list.dart index f077c79..9e0aa45 100644 --- a/lib/views/training/workouts/action_list.dart +++ b/lib/views/training/workouts/action_list.dart @@ -21,13 +21,13 @@ class ActionList extends StatefulWidget { // 从已存在的训练进入action list,会带上group信息去查询已存在的action list // 2023-12-04 如果是从计划页面跳转过来,就需要带上计划的编号已经训练日信息 final TrainingGroup groupItem; - final int? planId; + final TrainingPlan? planItem; final int? dayNumber; const ActionList({ super.key, required this.groupItem, - this.planId, + this.planItem, this.dayNumber, }); @@ -198,7 +198,7 @@ class _ActionListState extends State { }, ), // 2023-12-23 如果有planId,则从计划跳某一个训练日,再到这里,就不允许修改这个训练组,只能查看 - actions: widget.planId != null + actions: widget.planItem != null ? null : [ if (_isEditing) @@ -243,12 +243,11 @@ class _ActionListState extends State { MaterialPageRoute( builder: (context) => ActionFollowPracticeWithTTS( // 虽然计划编号、训练日 和训练编号都有传,但理论上两者不会同时存在也不会同时为空 - planId: widget.planId, + plan: widget.planItem, dayNumber: widget.dayNumber, // 有计划编号,就不传训练编号了 - groupId: widget.planId != null - ? null - : widget.groupItem.groupId, + group: + widget.planItem != null ? null : widget.groupItem, // 动作组数据是必须要传的 actionList: actionList, ), diff --git a/lib/views/training/workouts/index.dart b/lib/views/training/workouts/index.dart index 79f59d7..7a80c55 100644 --- a/lib/views/training/workouts/index.dart +++ b/lib/views/training/workouts/index.dart @@ -327,9 +327,8 @@ class _TrainingWorkoutsState extends State { }, // 长按点击弹窗提示是否删除 onLongPress: () async { - // 如果该基础活动有被使用,则不允许直接删除 - var list = - await _dbHelper.isGroupUsedByRawSQL(groupItem.group.groupId!); + // 如果该训练有被使用,则不允许直接删除 + var list = await _dbHelper.isGroupUsed(groupItem.group.groupId!); if (!mounted) return; if (list.isNotEmpty) { diff --git a/readme-merged.md b/readme-merged.md index 686b3c0..ec7e65a 100644 --- a/readme-merged.md +++ b/readme-merged.md @@ -154,3 +154,4 @@ - fix:修复跟练页面点击返回无暂停弹窗的问题。 - fix:修复饮食记录主页面表格模式时顶部概述部分的表格和下方餐次的表格位对齐额问题。 +- refactor:重新设计了训练日志宽表,计划可以任意删除修改,动作和训练未被使用时可删除。删除的计划和训练的训练记录的关键信息依旧保存在日志宽表中。