Bläddra i källkod

feat: update score when doing a repetition

Léo Salé 3 år sedan
förälder
incheckning
e324ce191c

+ 3 - 33
app/lib/challenge/models/challenge.dart

@@ -1,3 +1,5 @@
+import '../../exercises/exercises_validation/models/exercise.dart';
+
 class Challenge {
   final List<Exercise> exercises;
   final int bonusExercise;
@@ -15,38 +17,6 @@ class Challenge {
   }
 
   static List<Exercise> _getExercise(List<Map<String, dynamic>> exercises) {
-    return exercises.map((exercise) => Exercise.fromMap(exercise)).toList();
-  }
-}
-
-class Exercise {
-  final String description;
-  final Difficulty difficulty;
-  final String name;
-  final int repetitions;
-  final int sets;
-
-  const Exercise({
-    required this.description,
-    required this.difficulty,
-    required this.name,
-    required this.repetitions,
-    required this.sets,
-  });
-
-  factory Exercise.fromMap(Map<String, dynamic> data) {
-    return Exercise(
-      description: data["description"],
-      difficulty: Difficulty.values[data["difficulty"]],
-      name: data["name"],
-      repetitions: data["repetitions"],
-      sets: data["sets"],
-    );
+    return exercises.map((exercise) => Exercise.fromMap(exercise["id"], exercise)).toList();
   }
 }
-
-enum Difficulty {
-  easy,
-  normal,
-  hard,
-}

+ 1 - 1
app/lib/challenge/services/challenge_service.dart

@@ -25,7 +25,7 @@ class ChallengeService {
     return await Future.wait(
         List<DocumentReference<Map<String, dynamic>>>.from(challenge["list_exercises"]).map((exerciseRef) async {
       final exercise = await exerciseRef.get();
-      return exercise.data()!;
+      return {"id": exercise.id,...exercise.data()!};
     }));
   }
 }

+ 7 - 4
app/lib/challenge/widgets/challenge_exercises.dart

@@ -1,7 +1,8 @@
 import 'package:flutter/material.dart';
 import 'package:physigo/challenge/models/challenge.dart';
 import 'package:physigo/exercises/exercises_validation/exercise_validation_page.dart';
-import 'package:physigo/exercises/exercises_validation/models/squat.dart';
+
+import '../../exercises/exercises_validation/models/exercise.dart';
 
 class ChallengeExercises extends StatelessWidget {
   final Future<Challenge> Function() getChallenge;
@@ -63,9 +64,11 @@ class ExerciseTile extends StatelessWidget {
               )
             : null,
         trailing: const Icon(Icons.arrow_forward),
-        onTap: () {
-          Navigator.push(context, MaterialPageRoute(builder: (_) => ExerciseValidationPage(exercise: squat)));
-        },
+        onTap: exercise.startMovement != null
+            ? () {
+                Navigator.push(context, MaterialPageRoute(builder: (_) => ExerciseValidationPage(exercise: exercise)));
+              }
+            : null,
       ),
     );
   }

+ 40 - 31
app/lib/exercises/exercises_page.dart

@@ -1,19 +1,13 @@
 import 'package:flutter/material.dart';
-import 'package:physigo/exercises/exercises_validation/models/arm_raises.dart';
-import 'package:physigo/exercises/exercises_validation/models/exercise.dart';
-import 'package:physigo/exercises/exercises_validation/models/leg_curl.dart';
-import 'package:physigo/exercises/exercises_validation/models/leg_raises.dart';
-import 'package:physigo/exercises/exercises_validation/models/squat.dart';
-import 'package:physigo/walking/walking_page.dart';
 
+import '../walking/walking_page.dart';
 import 'exercises_validation/exercise_validation_page.dart';
-import 'exercises_validation/models/push_up.dart';
+import 'exercises_validation/models/exercise.dart';
+import 'services/exercises_service.dart';
 
 class ExercisesPage extends StatelessWidget {
   const ExercisesPage({Key? key}) : super(key: key);
 
-
-
   @override
   Widget build(BuildContext context) {
     return Scaffold(
@@ -71,12 +65,29 @@ class ExercisesPage extends StatelessWidget {
               ],
             ),
             const SizedBox(height: 48),
-            const WalkExerciseTile(),
-            ExerciseTile(exerciseName: "leg curl", exercise: legCurl, difficulty: 0),
-            ExerciseTile(exerciseName: "leg raises", exercise: legRaises, difficulty: 0),
-            ExerciseTile(exerciseName: "knee raises", exercise: pushUp, difficulty: 1),
-            ExerciseTile(exerciseName: "arm raises", exercise: armRaises, difficulty: 1),
-            ExerciseTile(exerciseName: "squat", exercise: squat, difficulty: 2),
+            FutureBuilder<List<Exercise>>(
+              future: ExercisesService.getExercises(),
+              builder: (context, snapshot) {
+                if (snapshot.connectionState == ConnectionState.waiting) {
+                  return const Center(child: CircularProgressIndicator());
+                }
+                final exercises = snapshot.data!;
+                exercises.sort((a, b) => a.difficulty.compareTo(b.difficulty));
+                return Flexible(
+                  child: Scrollbar(
+                    isAlwaysShown: true,
+                    thickness: 8,
+                    child: ListView(
+                      shrinkWrap: true,
+                      children: [
+                        const WalkExerciseTile(),
+                        ...exercises.map((exercise) => ExerciseTile(exercise: exercise))
+                      ],
+                    ),
+                  ),
+                );
+              },
+            )
           ],
         ),
       ),
@@ -85,20 +96,16 @@ class ExercisesPage extends StatelessWidget {
 }
 
 class ExerciseTile extends StatelessWidget {
-  final String exerciseName;
   final Exercise exercise;
-  final int difficulty;
   const ExerciseTile({
-    required this.exerciseName,
     required this.exercise,
-    required this.difficulty,
     Key? key,
   }) : super(key: key);
 
   Color get cardColor {
-    if (difficulty == 0) return Colors.lightGreen;
-    if (difficulty == 1) return Colors.amber;
-    if (difficulty == 2) return Colors.red;
+    if (exercise.difficulty == 0) return Colors.lightGreen;
+    if (exercise.difficulty == 1) return Colors.amber;
+    if (exercise.difficulty == 2) return Colors.red;
     return Colors.blueGrey;
   }
 
@@ -112,7 +119,7 @@ class ExerciseTile extends StatelessWidget {
           iconColor: Colors.white,
           textColor: Colors.white,
           title: Text(
-            exerciseName.toUpperCase(),
+            exercise.name.toUpperCase(),
             textAlign: TextAlign.center,
             style: const TextStyle(
               fontSize: 18,
@@ -121,14 +128,16 @@ class ExerciseTile extends StatelessWidget {
             ),
           ),
           trailing: const Icon(Icons.arrow_forward),
-          onTap: () {
-            Navigator.push(
-              context,
-              MaterialPageRoute(
-                builder: (_) => ExerciseValidationPage(exercise: exercise),
-              ),
-            );
-          },
+          onTap: exercise.startMovement != null // not all exercises have validation right now
+              ? () {
+                  Navigator.push(
+                    context,
+                    MaterialPageRoute(
+                      builder: (_) => ExerciseValidationPage(exercise: exercise),
+                    ),
+                  );
+                }
+              : null,
         ),
       ),
     );

+ 0 - 28
app/lib/exercises/exercises_validation/models/arm_raises.dart

@@ -1,28 +0,0 @@
-import 'package:physigo/exercises/exercises_validation/models/exercise.dart';
-
-const startMovement = CriteriaDistance(
-  jointStart: AuthorizedTypeIndex.rightWrist,
-  jointEnd: AuthorizedTypeIndex.rightHip,
-  axis: 1,
-  threshold: 40,
-  comparator: Comparator.lesser,
-);
-const endMovement = CriteriaDistance(
-  jointStart: AuthorizedTypeIndex.rightWrist,
-  jointEnd: AuthorizedTypeIndex.rightShoulder,
-  axis: 1,
-  threshold: 80,
-  comparator: Comparator.lesser,
-);
-
-final armRaises = Exercise(
-  reps: 3,
-  series: 3,
-  startMovement: startMovement,
-  endMovement: endMovement,
-  jointsOnScreen: [
-    AuthorizedTypeIndex.rightWrist,
-    AuthorizedTypeIndex.rightHip,
-    AuthorizedTypeIndex.rightShoulder,
-  ],
-);

+ 149 - 8
app/lib/exercises/exercises_validation/models/exercise.dart

@@ -19,34 +19,121 @@ enum AuthorizedTypeIndex {
   rightAnkle,
 }
 
+AuthorizedTypeIndex typeIndexFromString(String typeIndex) {
+  switch (typeIndex) {
+    case "nose":
+      return AuthorizedTypeIndex.nose;
+    case "leftShoulder":
+      return AuthorizedTypeIndex.leftShoulder;
+    case "rightShoulder":
+      return AuthorizedTypeIndex.rightShoulder;
+    case "leftElbow":
+      return AuthorizedTypeIndex.leftElbow;
+    case "rightElbow":
+      return AuthorizedTypeIndex.rightElbow;
+    case "leftWrist":
+      return AuthorizedTypeIndex.leftWrist;
+    case "rightWrist":
+      return AuthorizedTypeIndex.rightWrist;
+    case "leftHip":
+      return AuthorizedTypeIndex.leftHip;
+    case "rightHip":
+      return AuthorizedTypeIndex.rightHip;
+    case "leftKnee":
+      return AuthorizedTypeIndex.leftKnee;
+    case "rightKnee":
+      return AuthorizedTypeIndex.rightKnee;
+    case "leftAnkle":
+      return AuthorizedTypeIndex.leftAnkle;
+    case "rightAnkle":
+      return AuthorizedTypeIndex.rightAnkle;
+    default:
+      throw const FormatException("Incorrect type name");
+  }
+}
+
 enum Comparator {
   greater,
   lesser,
 }
 
+Comparator comparatorFromString(String comparator) {
+  switch (comparator) {
+    case "greater":
+      return Comparator.greater;
+    case "lesser":
+      return Comparator.lesser;
+    default:
+      throw const FormatException("Incorrect comparator name");
+  }
+}
+
 class Exercise {
-  final int reps;
-  final int series;
-  final Criteria startMovement;
-  final Criteria endMovement;
+  final int repetitions;
+  final int sets;
+  final Criteria? startMovement;
+  final Criteria? endMovement;
+  final String description;
+  final String name;
+  final int difficulty;
+  final String id;
   late final List<PoseLandmarkType> jointsOnScreen;
 
   Exercise({
-    required this.reps,
-    required this.series,
+    required this.repetitions,
+    required this.sets,
     required this.startMovement,
     required this.endMovement,
+    required this.description,
+    required this.name,
+    required this.difficulty,
+    required this.id,
     required List<AuthorizedTypeIndex> jointsOnScreen,
   }) {
     this.jointsOnScreen = jointsOnScreen.map((joint) => authorizedType[joint.index]).toList();
   }
 
   bool isAtStartMovement(MeanFilteredData meanFilteredData) {
-    return startMovement.isAtPosition(meanFilteredData);
+    final start = startMovement;
+    if (start != null) {
+      return start.isAtPosition(meanFilteredData);
+    }
+    return false;
   }
 
   bool isAtEndMovement(MeanFilteredData meanFilteredData) {
-    return endMovement.isAtPosition(meanFilteredData);
+    final end = endMovement;
+    if (end != null) {
+      return end.isAtPosition(meanFilteredData);
+    }
+    return false;
+  }
+
+  Map<String, dynamic> toMap() {
+    return {
+      "repetitions": repetitions,
+      "sets": sets,
+      "start_movement": startMovement?.toMap(),
+      "end_movement": endMovement?.toMap(),
+      "description": description,
+      "name": name,
+      "difficulty": difficulty,
+      "joints_on_screen": jointsOnScreen.map((j) => j.name).toList(),
+    };
+  }
+
+  factory Exercise.fromMap(String id, Map<String, dynamic> map) {
+    return Exercise(
+      repetitions: map["repetitions"],
+      sets: map["sets"],
+      startMovement: map.containsKey("start_movement") ? Criteria.fromMap(map["start_movement"]) : null,
+      endMovement: map.containsKey("end_movement") ? Criteria.fromMap(map["end_movement"]) : null,
+      description: map["description"],
+      name: map["name"],
+      difficulty: map["difficulty"],
+      id: id,
+      jointsOnScreen: map.containsKey("joints_on_screen") ? List<String>.from(map["joints_on_screen"]).map((e) => typeIndexFromString(e)).toList() : [],
+    );
   }
 
   static const authorizedType = [
@@ -68,6 +155,17 @@ class Exercise {
 
 abstract class Criteria {
   bool isAtPosition(MeanFilteredData meanFilteredData);
+  Map<String, dynamic> toMap();
+  static Criteria fromMap(Map<String, dynamic> map) {
+    switch (map["type"]) {
+      case "distance":
+        return CriteriaDistance.fromMap(map);
+      case "angle":
+        return CriteriaAngle.fromMap(map);
+      default:
+        throw FormatException("Type should be distance or angle but is ${map['type']}");
+    }
+  }
 }
 
 class CriteriaDistance implements Criteria {
@@ -100,6 +198,27 @@ class CriteriaDistance implements Criteria {
       return distance < threshold;
     }
   }
+
+  @override
+  Map<String, dynamic> toMap() {
+    return {
+      "joint_start": jointStart.name,
+      "joint_end": jointEnd.name,
+      "threshold": threshold,
+      "comparator": comparator.name,
+      "type": "distance",
+    };
+  }
+
+  factory CriteriaDistance.fromMap(Map<String, dynamic> map) {
+    return CriteriaDistance(
+      jointStart: typeIndexFromString(map["joint_start"]),
+      jointEnd: typeIndexFromString(map["joint_end"]),
+      axis: map["axis"],
+      threshold: map["threshold"],
+      comparator: comparatorFromString(map["comparator"]),
+    );
+  }
 }
 
 class CriteriaAngle implements Criteria {
@@ -142,4 +261,26 @@ class CriteriaAngle implements Criteria {
       return angle < threshold;
     }
   }
+
+  @override
+  Map<String, dynamic> toMap() {
+    return {
+      "joint_start": jointStart.name,
+      "joint_center": jointCenter.name,
+      "joint_end": jointEnd.name,
+      "threshold": threshold,
+      "comparator": comparator.name,
+      "type": "angle",
+    };
+  }
+
+  factory CriteriaAngle.fromMap(Map<String, dynamic> map) {
+    return CriteriaAngle(
+      jointStart: typeIndexFromString(map["joint_start"]),
+      jointCenter: typeIndexFromString(map["joint_center"]),
+      jointEnd: typeIndexFromString(map["joint_end"]),
+      threshold: map["threshold"],
+      comparator: comparatorFromString(map["comparator"]),
+    );
+  }
 }

+ 0 - 30
app/lib/exercises/exercises_validation/models/leg_curl.dart

@@ -1,30 +0,0 @@
-import 'package:physigo/exercises/exercises_validation/models/exercise.dart';
-
-const startMovement = CriteriaDistance(
-  jointStart: AuthorizedTypeIndex.rightAnkle,
-  jointEnd: AuthorizedTypeIndex.leftAnkle,
-  axis: 1,
-  threshold: 40,
-  comparator: Comparator.lesser,
-);
-
-const endMovement = CriteriaDistance(
-  jointStart: AuthorizedTypeIndex.rightAnkle,
-  jointEnd: AuthorizedTypeIndex.rightKnee,
-  axis: 1,
-  threshold: 40,
-  comparator: Comparator.lesser,
-);
-
-final legCurl = Exercise(
-  reps: 3,
-  series: 3,
-  startMovement: startMovement,
-  endMovement: endMovement,
-  jointsOnScreen: [
-    AuthorizedTypeIndex.rightAnkle,
-    AuthorizedTypeIndex.leftAnkle,
-    AuthorizedTypeIndex.rightKnee,
-    AuthorizedTypeIndex.rightHip,
-  ],
-);

+ 0 - 30
app/lib/exercises/exercises_validation/models/leg_raises.dart

@@ -1,30 +0,0 @@
-import 'package:physigo/exercises/exercises_validation/models/exercise.dart';
-
-const startMovement = CriteriaDistance(
-  jointStart: AuthorizedTypeIndex.rightAnkle,
-  jointEnd: AuthorizedTypeIndex.leftAnkle,
-  axis: 1,
-  threshold: 40,
-  comparator: Comparator.lesser,
-);
-
-const endMovement = CriteriaDistance(
-  jointStart: AuthorizedTypeIndex.rightAnkle,
-  jointEnd: AuthorizedTypeIndex.rightKnee,
-  axis: 1,
-  threshold: 40,
-  comparator: Comparator.lesser,
-);
-
-final legRaises = Exercise(
-  reps: 3,
-  series: 3,
-  startMovement: startMovement,
-  endMovement: endMovement,
-  jointsOnScreen: [
-    AuthorizedTypeIndex.rightAnkle,
-    AuthorizedTypeIndex.leftAnkle,
-    AuthorizedTypeIndex.rightKnee,
-    AuthorizedTypeIndex.rightHip,
-  ],
-);

+ 0 - 31
app/lib/exercises/exercises_validation/models/push_up.dart

@@ -1,31 +0,0 @@
-import 'package:physigo/exercises/exercises_validation/models/exercise.dart';
-
-const startMovement = CriteriaDistance(
-  jointStart: AuthorizedTypeIndex.rightShoulder,
-  jointEnd: AuthorizedTypeIndex.rightElbow,
-  axis: 1,
-  threshold: 130,
-  comparator: Comparator.greater,
-);
-
-const endMovement = CriteriaDistance(
-  jointStart: AuthorizedTypeIndex.rightShoulder,
-  jointEnd: AuthorizedTypeIndex.rightElbow,
-  axis: 1,
-  threshold: 110,
-  comparator: Comparator.lesser,
-);
-
-final pushUp = Exercise(
-  reps: 3,
-  series: 3,
-  startMovement: startMovement,
-  endMovement: endMovement,
-  jointsOnScreen: [
-    AuthorizedTypeIndex.rightShoulder,
-    AuthorizedTypeIndex.rightElbow,
-    AuthorizedTypeIndex.rightWrist,
-    AuthorizedTypeIndex.leftShoulder,
-    AuthorizedTypeIndex.leftElbow,
-  ],
-);

+ 0 - 31
app/lib/exercises/exercises_validation/models/squat.dart

@@ -1,31 +0,0 @@
-import 'package:physigo/exercises/exercises_validation/models/exercise.dart';
-
-const startMovement = CriteriaAngle(
-  jointStart: AuthorizedTypeIndex.rightShoulder,
-  jointCenter: AuthorizedTypeIndex.rightHip,
-  jointEnd: AuthorizedTypeIndex.rightKnee,
-  threshold: 320,
-  comparator: Comparator.greater,
-);
-
-const endMovement = CriteriaDistance(
-  jointStart: AuthorizedTypeIndex.leftHip,
-  jointEnd: AuthorizedTypeIndex.leftKnee,
-  axis: 1,
-  threshold: 80,
-  comparator: Comparator.lesser,
-);
-
-final squat = Exercise(
-  reps: 3,
-  series: 3,
-  startMovement: startMovement,
-  endMovement: endMovement,
-  jointsOnScreen: [
-    AuthorizedTypeIndex.rightShoulder,
-    AuthorizedTypeIndex.rightHip,
-    AuthorizedTypeIndex.rightKnee,
-    AuthorizedTypeIndex.leftHip,
-    AuthorizedTypeIndex.leftKnee,
-  ],
-);

+ 2 - 2
app/lib/exercises/exercises_validation/widgets/exercise_indicator.dart

@@ -59,11 +59,11 @@ class ExerciseIndicator extends StatelessWidget {
             return Column(
               children: [
                 Text(
-                  "${repCounter % exercise.reps}/${exercise.reps}",
+                  "${repCounter % exercise.repetitions}/${exercise.repetitions}",
                   style: const TextStyle(fontSize: 40),
                 ),
                 Text(
-                  "${repCounter ~/ exercise.reps}/${exercise.series}",
+                  "${repCounter ~/ exercise.repetitions}/${exercise.sets}",
                   style: const TextStyle(fontSize: 40),
                 ),
               ],

+ 8 - 4
app/lib/exercises/exercises_validation/widgets/pose_detector.dart

@@ -2,6 +2,7 @@ import 'dart:async';
 
 import 'package:body_detection/models/pose_landmark.dart';
 import 'package:physigo/exercises/exercises_validation/models/exercise.dart';
+import 'package:physigo/exercises/services/exercises_service.dart';
 import 'package:rxdart/rxdart.dart';
 import 'package:body_detection/body_detection.dart';
 import 'package:body_detection/models/image_result.dart';
@@ -84,6 +85,9 @@ class _PoseDetectorState extends State<PoseDetector> {
         .pairwise()
         .where((event) => event.first == StepExercise.end && event.last == StepExercise.start)
         .scan((int accumulated, value, index) => accumulated + 1, 0);
+    _repCounter.listen((_) {
+      ExercisesService.addScoreToExercise(widget.exercise, 5 * (widget.exercise.difficulty + 1));
+    });
   }
 
   void _handleNotInPlacePosition(List<Object?> event) {
@@ -220,10 +224,10 @@ class _PoseDetectorState extends State<PoseDetector> {
             Center(
               child: CustomPaint(
                 child: _cameraImage,
-                // foregroundPainter: PosePainter(
-                //   pose: _detectedPose,
-                //   imageSize: _imageSize,
-                // ),
+                foregroundPainter: PosePainter(
+                  pose: _detectedPose,
+                  imageSize: _imageSize,
+                ),
               ),
             ),
             ExerciseIndicator(

+ 35 - 0
app/lib/exercises/services/exercises_service.dart

@@ -0,0 +1,35 @@
+import 'package:cloud_firestore/cloud_firestore.dart';
+import 'package:firebase_auth/firebase_auth.dart';
+import 'package:physigo/exercises/exercises_validation/models/exercise.dart';
+
+class ExercisesService {
+  static final _db = FirebaseFirestore.instance;
+  static final _exercises = _db.collection("Exercises");
+  static final _exercisesScores = _db.collection("ExercisesScores");
+
+  static Future<List<Exercise>> getExercises() async {
+    final exercisesSnapshot = await _exercises.get();
+    if (exercisesSnapshot.docs.isEmpty) {
+      return [];
+    }
+    final exercises = exercisesSnapshot.docs.map((e) => Exercise.fromMap(e.id, e.data())).toList();
+    return exercises;
+  }
+
+  static Future<void> addScoreToExercise(Exercise exercise, int score) async {
+    final userId = FirebaseAuth.instance.currentUser!.uid;
+    final exerciseScoreSnapshot =
+        await _exercisesScores.where("user", isEqualTo: userId).where("exercise", isEqualTo: exercise.id).get();
+    if (exerciseScoreSnapshot.docs.isEmpty) {
+      await _exercisesScores.add({"score": score, "user": userId, "exercise": exercise.id});
+    } else {
+      final exerciseScore = exerciseScoreSnapshot.docs.first;
+      _exercisesScores.doc(exerciseScore.id).update({"score": exerciseScore.get("score") + score});
+    }
+    final user = await _db.collection("profileInfo").doc(FirebaseAuth.instance.currentUser?.uid).get();
+    _db
+        .collection("profileInfo")
+        .doc(FirebaseAuth.instance.currentUser?.uid)
+        .update({"total_points": user.get("total_points") + score});
+  }
+}