2024-06-05 13:28:22 +02:00
15 changed files with 290 additions and 287 deletions

View File

@@ -24,6 +24,7 @@
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/border_background.xml" value="0.2475" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/border_background_2.xml" value="0.2475" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/border_background_3.xml" value="0.2475" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/border_background_circle.xml" value="0.2475" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/box_background.xml" value="0.2555" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/darkred_button_gradient.xml" value="0.346" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/fitbot_launcher_background.xml" value="0.2475" />
@@ -33,6 +34,9 @@
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/ic_baseline_settings_48.xml" value="0.25" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/ic_baseline_star_rate_48.xml" value="0.25" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/ic_launcher_background.xml" value="0.25" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/progress_circle.xml" value="0.2475" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/progress_circle_burst.xml" value="0.2475" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/progress_circle_burst_good.xml" value="0.1" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/rectangle.xml" value="0.2395" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/red_button_gradient.xml" value="0.346" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_bicepvideo.xml" value="0.22826086956521738" />

View File

@@ -40,6 +40,12 @@ public class ExerciseManager {
public static final float EXERCISE_ERROR_MARGIN = 1.0f;
public static final float EXERCISE_TIME_SCALING_FACTOR = 1.0f;
// Fields representing the statistics of the user
public static int TOTAL_REPETITIONS_REQUIRED = 0;
public static int TOTAL_REPETITIONS_PERFORMED = 0;
public static int TOTAL_EXERCISES_PREFORMED = 0;
/**
* Function for sending an HTTP request to the server.
*
@@ -60,6 +66,7 @@ public class ExerciseManager {
// Send a body if it is present
if (body != null)
connection.getOutputStream().write(body.getBytes());
InputStream stream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
StringBuilder builder = new StringBuilder();

View File

@@ -1,49 +1,60 @@
package com.example.fitbot.ui.activities;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.app.Dialog;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.VideoView;
import com.aldebaran.qi.Future;
import com.aldebaran.qi.sdk.QiContext;
import com.aldebaran.qi.sdk.QiSDK;
import com.aldebaran.qi.sdk.RobotLifecycleCallbacks;
import com.aldebaran.qi.sdk.builder.AnimateBuilder;
import com.aldebaran.qi.sdk.builder.AnimationBuilder;
import com.aldebaran.qi.sdk.design.activity.RobotActivity;
import com.aldebaran.qi.sdk.design.activity.conversationstatus.SpeechBarDisplayStrategy;
import com.aldebaran.qi.sdk.object.actuation.Animate;
import com.aldebaran.qi.sdk.object.actuation.Animation;
import com.example.fitbot.R;
import com.example.fitbot.exercise.Exercise;
import com.example.fitbot.exercise.ExerciseManager;
import com.example.fitbot.pepper.Pepper;
import com.example.fitbot.ui.components.ExerciseStatusElement;
import com.example.fitbot.util.NavigationManager;
import com.example.fitbot.util.processing.InputProcessor;
import org.joml.Vector3f;
public class FitnessActivity extends RobotActivity implements RobotLifecycleCallbacks {
// Private fields for the FitnessActivity class.
private ExerciseStatusElement exerciseStatusElement;
private InputProcessor motionProcessor;
private Exercise currentExercise;
// Progress circle for the exercises
private ProgressBar progressCircle;
private TextView progressText;
private int progress = 0;
private final int maxProgress = 10;
// Exercise status element data
private TextView exerciseMuscleGroupTextView;
private TextView exerciseNameTextView;
private TextView exerciseShortDescriptionTextView;
//private TextView exerciseDescriptionTextView;
private static String exerciseVideoUrl;
private Animate animate;
private VideoView videoView;
private final Object lock = new Object();
@@ -67,6 +78,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
super.onCreate(savedInstanceState);
QiSDK.register(this, this);
setContentView(R.layout.activity_fitness);
videoView = findViewById(R.id.videoView);
// Fill empty objects with exercise data
this.exerciseNameTextView = findViewById(R.id.textViewFitnessTitle);
@@ -76,6 +88,12 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
// Set the repetition count for the exercise
EXERCISE_REP = 1;
progressCircle = findViewById(R.id.progressCircle);
progressText = findViewById(R.id.progressText);
progressCircle.setMax(maxProgress);
updateProgress();
// Set color of loading circle
ProgressBar loadingCircle = findViewById(R.id.loadingCircle);
loadingCircle.setIndeterminateTintList(ColorStateList.valueOf(Color.RED));
@@ -104,30 +122,39 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
// Provide the context so that all queued actions can be performed.
Pepper.provideContext(qiContext, this.getClass());
exerciseStatusElement = findViewById(R.id.personalMotionPreviewElement);
// Initialize the element whenever it has been added to the screen.
// This will provide the element with the appropriate dimensions for drawing
// the canvas properly.
exerciseStatusElement.post(() -> {
this.fetchExerciseAsync((exercise) -> {
// Acquire paths from the exercise and provide them to the motion processor
Vector3f[][] vectors = new Vector3f[][]{exercise.leftPath.getAngleVectors(), exercise.rightPath.getAngleVectors()};
this.fetchExerciseAsync((exercise) -> {
// Acquire paths from the exercise and provide them to the motion processor
motionProcessor = new InputProcessor(SENSOR_SAMPLE_RATE, this);
motionProcessor.useExercise(exercise);
/* TODO: Remove if not needed */
motionProcessor.setRecording(true, 10);
motionProcessor.startListening();
motionProcessor = new InputProcessor(vectors, exercise.exerciseTimeInSeconds, SENSOR_SAMPLE_RATE);
if ( videoView.isPlaying() )
{
Animation animationarmraise = AnimationBuilder.with(qiContext) // Create the builder with the context.
.withResources(R.raw.armraise) // Set the animation resource.
.build(); // Build the animation.
exerciseStatusElement.initialize(exercise, motionProcessor, EXERCISE_COUNT);
motionProcessor.useExercise(exercise);
/* TODO: Remove if not needed */motionProcessor.setRecording(true, 10);
motionProcessor.setInputHandler(exerciseStatusElement);
motionProcessor.startListening();
animate = AnimateBuilder.with(qiContext) // Create the builder with the context.
.withAnimation(animationarmraise) // Set the animation.
.build(); // Build the animate action.
}, (n) -> {
int randomMessageIndex = (int) Math.floor(Math.random() * EXERCISE_NOT_FOUND_MESSAGES.length);
Pepper.say(EXERCISE_NOT_FOUND_MESSAGES[randomMessageIndex]);
Pepper.say(EXERCISE_NOT_FOUND_SEEK_HELP_MESSAGE);
NavigationManager.navigateToActivity(this, EndScreenActivity.class);
});
Future<Void> animateFuture = animate.async().run();
}
else
{
Log.e("FitnessActivity", "VideoView is null. Check your layout XML.");
}
}, (n) -> {
int randomMessageIndex = (int) Math.floor(Math.random() * EXERCISE_NOT_FOUND_MESSAGES.length);
Pepper.say(EXERCISE_NOT_FOUND_MESSAGES[randomMessageIndex]);
Pepper.say(EXERCISE_NOT_FOUND_SEEK_HELP_MESSAGE);
NavigationManager.navigateToActivity(this, EndScreenActivity.class);
});
}
@@ -142,17 +169,16 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
new Thread(() -> {
Exercise exercise = ExerciseManager.fetchExerciseFromDatabase();
if (exercise == null) {
onFailedFetch.handle(null);
runOnUiThread(() -> onFailedFetch.handle(null));
} else {
onSuccessfulFetch.handle(exercise);
this.runOnUiThread(() -> {
runOnUiThread(() -> {
onSuccessfulFetch.handle(exercise);
exerciseNameTextView.setText(exercise.name);
exerciseShortDescriptionTextView.setText(exercise.shortDescription);
// exerciseDescriptionTextView.setText(exercise.description);
exerciseVideoUrl = exercise.videoUrl;
// Play the video
VideoView videoView = findViewById(R.id.videoView);
playVideo(videoView, this);
// When the video has started playing remove the loading circle
@@ -165,13 +191,10 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
return false;
});
videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
if (EXERCISE_REP < EXERCISE_COUNT) {
videoView.start(); // start the video again
EXERCISE_REP++;
}
videoView.setOnCompletionListener(mp -> {
if (EXERCISE_REP < EXERCISE_COUNT) {
videoView.start(); // start the video again
EXERCISE_REP++;
}
});
});
@@ -242,4 +265,36 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
});
dialog.show();
}
public void incrementProgress(View view) {
if (progress < maxProgress) {
progress++;
triggerColorBurst(false);
updateProgress();
}
}
private void updateProgress() {
progressCircle.setProgress(progress);
progressText.setText(progress + "/" + maxProgress);
}
private void triggerColorBurst(boolean isGoodRep) {
if (isGoodRep) {
progressCircle.setProgressDrawable(ContextCompat.getDrawable(this, R.drawable.progress_circle_good));
} else {
progressCircle.setProgressDrawable(ContextCompat.getDrawable(this, R.drawable.progress_circle_bad));
}
ObjectAnimator animator = ObjectAnimator.ofFloat(progressCircle, "alpha", 1f, 0f, 1f);
animator.setDuration(500); // Burst duration
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
progressCircle.setProgressDrawable(ContextCompat.getDrawable(FitnessActivity.this, R.drawable.progress_circle));
}
});
animator.start();
}
}

View File

@@ -1,159 +0,0 @@
package com.example.fitbot.ui.components;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import com.example.fitbot.exercise.Exercise;
import com.example.fitbot.pepper.Pepper;
import com.example.fitbot.ui.activities.EndScreenActivity;
import com.example.fitbot.ui.activities.FitnessActivity;
import com.example.fitbot.ui.activities.MainActivity;
import com.example.fitbot.util.NavigationManager;
import com.example.fitbot.util.processing.IInputHandler;
import com.example.fitbot.util.processing.InputProcessor;
import org.joml.Vector3f;
public class ExerciseStatusElement extends View implements IInputHandler {
// Fields regarding Exercise and speech handling.
private InputProcessor motionProcessor;
private Exercise exercise;
private int exerciseCount;
private FitnessActivity parentActivity;
private final Paint userProgressPaint = new Paint();
private final Paint borderPaint = new Paint();
private final Paint backgroundPaint = new Paint();
private static final String[] STARTING_PHRASES = {
"Veel success met de oefening!",
"Je kan het!",
"Veel plezier!"
};
public ExerciseStatusElement(Context context, AttributeSet attrs) {
super(context, attrs);
if (context instanceof Activity) {
this.parentActivity = (FitnessActivity) context;
}
this.userProgressPaint.setColor(0xFFFF0000); // Red
this.userProgressPaint.setStyle(Paint.Style.FILL);
this.userProgressPaint.setStrokeWidth(5.0f);
this.userProgressPaint.setAntiAlias(true);
// Target paint is the filling of the target path.
this.borderPaint.setColor(-1);
this.borderPaint.setStyle(Paint.Style.STROKE);
this.borderPaint.setStrokeWidth(5.0f);
this.borderPaint.setAntiAlias(true);
this.backgroundPaint.setColor(0xFF000000); // Black
}
/**
* Method for initializing the PersonalMotionPreviewElement.
* This method has to be called with a "post" function when the element has been
* created, otherwise the dimensions of the element aren't initialized yet, which
* will cause the vertex projections to fail (0 width and height).
*
* @param exercise The exercise that the user is currently performing.
* @param motionProcessor The motion processor that will be used to process the user's motion.
* @param exerciseCount The total amount of exercises that the user has to perform.
*/
public void initialize(@Nullable Exercise exercise, InputProcessor motionProcessor, int exerciseCount) {
Log.i("PersonalMotionPreviewElement", "Creating new PersonalMotionPreviewElement.");
this.motionProcessor = motionProcessor;
this.exercise = exercise;
this.exerciseCount = exerciseCount;
Pepper.say(STARTING_PHRASES[(int) Math.floor(Math.random() * STARTING_PHRASES.length)]);
// Handler that is called every time the motion processor receives new data.
}
/**
* Method for setting the gesture path that will be drawn on the canvas.
*
* @param exercise The exercise that the user is currently performing.
*/
public void setExercise(Exercise exercise) {
this.motionProcessor.useExercise(exercise);
this.exercise = exercise;
Log.i("MotionProcessor", "Updating exercise in ExerciseStatusElement");
}
@Override
public void onDraw(Canvas canvas) {
canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundPaint);
this.setBackgroundColor(0xFF000000); // Black
/*if (this.exercise == null)
return;*/
/*
// Draw target circle
float targetRadius = (this.screenDimensions.x + this.screenDimensions.y) / 5.0f;
canvas.drawCircle(this.screenDimensions.x / 2, this.screenDimensions.y / 2, targetRadius, this.targetPaint);
canvas.drawCircle(this.screenDimensions.x / 2, this.screenDimensions.y / 2, (targetRadius * exerciseProgress.get()/1000.0f), this.referencePaint);
referencePaint.setColor(
Color.argb(
255,
(int)(255 * (1.0 - exerciseProgress.get()/1000.0f)),
(int)(255 * exerciseProgress.get()/1000.0f),
0
)
);*/
this.invalidate();
}
@Override
public void accept(Vector3f rotationVector, int sensorId) {
Log.i("MotionProcessor", "Rotation vector received: " + rotationVector);
Log.i("MotionProcessor", "Last error offset:" + this.motionProcessor.getError(sensorId, this.motionProcessor.secondsPassed()));
// Check whether the current exercise has been completed.
// This is determined by the duration of the exercise, and the amount of time that has passed.
// The duration of the exercise originates from the database, and is stored in seconds.
// Whenever 'useExercise' is called, the timer resets and this method will be called again.
if (this.motionProcessor.hasFinished() && !this.motionProcessor.isRecording()) {
// If for some reason the parent activity is not defined,
// move back to the main screen.
if (this.parentActivity == null) {
// Move to main screen
Log.i("MotionProcessor", "Parent activity was null.");
NavigationManager.navigateToActivity(getContext(), MainActivity.class);
return;
}
// Move on to the next exercise, or finish.
if (this.exerciseCount > 0) {
this.exerciseCount--;
this.parentActivity.fetchExerciseAsync((newExercise) -> {
this.motionProcessor.useExercise(newExercise);
// Whenever the database retrieval failed, we return to the main screen.
}, (failed) -> {
// Move to main screen
Log.i("MotionProcessor", "Failed to fetch exercise from database");
NavigationManager.navigateToActivity(parentActivity, MainActivity.class);
});
} else {
// Finish the exercise.
Log.i("MotionProcessor", "Exercise has finished");
NavigationManager.navigateToActivity(parentActivity, EndScreenActivity.class);
}
}
}
}

View File

@@ -53,13 +53,15 @@ public class AnglePath {
throw new IllegalArgumentException("Input string must contain 2 elements");
Vector3f[][] angles = new Vector3f[ExerciseManager.SENSOR_COUNT][];
for ( int dataArrayIdx = 0; dataArrayIdx < parsed.getAsJsonArray().size(); dataArrayIdx++)
{
JsonArray array = parsed.getAsJsonArray().get(dataArrayIdx).getAsJsonObject().get("data").getAsJsonArray();
angles[dataArrayIdx] = new Vector3f[array.size()];
int deviceIdx = parsed.getAsJsonArray().get(dataArrayIdx).getAsJsonObject().get("deviceId").getAsInt();
angles[deviceIdx] = new Vector3f[array.size()];
for (int i = 0; i < array.size(); i++) {
JsonArray vec = array.get(i).getAsJsonArray();
angles[dataArrayIdx][i] = new Vector3f(vec.get(0).getAsFloat(), vec.get(1).getAsFloat(), vec.get(2).getAsFloat());
angles[deviceIdx][i] = new Vector3f(vec.get(0).getAsFloat(), vec.get(1).getAsFloat(), vec.get(2).getAsFloat());
}
}
return new AnglePath[] {new AnglePath(angles[0]), new AnglePath(angles[1])};

View File

@@ -2,15 +2,18 @@ package com.example.fitbot.util.processing;
import android.util.Log;
import com.aldebaran.qi.sdk.object.geometry.Vector3;
import com.example.fitbot.exercise.Exercise;
import com.example.fitbot.exercise.ExerciseManager;
import com.example.fitbot.pepper.Pepper;
import com.example.fitbot.ui.activities.EndScreenActivity;
import com.example.fitbot.ui.activities.FitnessActivity;
import com.example.fitbot.ui.activities.MainActivity;
import com.example.fitbot.util.NavigationManager;
import com.example.fitbot.util.server.WebServer;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import org.jetbrains.annotations.NotNull;
import org.joml.Vector3f;
@@ -26,9 +29,21 @@ public class InputProcessor {
private final float sampleRate; // The sample rate of the motion sensor
private float exerciseDurationInSeconds;
private int repetitionsRemaining = 0;
private int exercisesRemaining = 0;
private float exerciseScore = 0.0F;
private final FitnessActivity parentActivity;
/**
* The phrases that are said by the robot whenever the exercise starts.
*/
private static final String[] STARTING_PHRASES = {
"Veel success met de oefening!",
"Je kan het!",
"Veel plezier!"
};
/**
* This field is used to determine if the motion data is being recorded.
* If this is the case, instead of functioning normally, the element
@@ -48,8 +63,6 @@ public class InputProcessor {
private double secondsPassed = 0.0D;
private long lastTime;
private IInputHandler motionDataConsumer;
private static final String[] REQUIRED_SENSOR_JSON_PROPERTIES =
{"rotationX", "rotationY", "rotationZ", "deviceId"};
@@ -60,34 +73,50 @@ public class InputProcessor {
/**
* Constructor for the motion processor.
*
* @param paths The target paths of the motion data.
* The length of this array must be equal to the
* amount of sensors available.
* @param inputSampleRate The sample rate of the motion sensor.
*/
public InputProcessor(Vector3f[][] paths, float exerciseTime, float inputSampleRate) {
this.selfRotationVectorPaths = new ArrayList[2];
this.selfRotationVectorPaths[0] = new ArrayList<>();
this.selfRotationVectorPaths[1] = new ArrayList<>();
targetRotationVectorPaths = paths;
public InputProcessor(float inputSampleRate, FitnessActivity parentActivity) {
this.sampleRate = inputSampleRate;
this.exerciseDurationInSeconds = exerciseTime;
this.parentActivity = parentActivity;
}
/**
* Function for setting the exercise to use.
* This updates the user and target path and the
* duration of the exercise.
* <p>
* This function is only initially used to select the starting exercise;
* the exercises that follow are determined by a private method 'nextExercise'
*
* @param exercise The exercise to use the paths for.
*/
public void useExercise(Exercise exercise) {
if ( this.recordingMovement )
if (this.recordingMovement)
throw new IllegalStateException("Cannot change exercise while recording movement.");
this.exercisesRemaining = 1;
this.nextExercise(exercise);
Pepper.say(STARTING_PHRASES[(int) Math.floor(Math.random() * STARTING_PHRASES.length)]);
}
/**
* Moves on to the next exercise without changing the remaining exercises.
*
* @param exercise The exercise to move on to.
*/
private void nextExercise(Exercise exercise) {
if (this.exercisesRemaining-- <= 0) {
NavigationManager.navigateToActivity(this.parentActivity, EndScreenActivity.class);
}
ExerciseManager.TOTAL_REPETITIONS_REQUIRED += ExerciseManager.DEFAULT_EXERCISE_REPETITIONS;
ExerciseManager.TOTAL_EXERCISES_PREFORMED++;
this.selfRotationVectorPaths[0] = new ArrayList<>();
this.selfRotationVectorPaths[1] = new ArrayList<>();
this.repetitionsRemaining = ExerciseManager.DEFAULT_EXERCISE_REPETITIONS;
this.targetRotationVectorPaths = new Vector3f[2][exercise.rightPath.getAngleVectors().length];
this.targetRotationVectorPaths[0] = exercise.leftPath.getAngleVectors();
this.targetRotationVectorPaths[1] = exercise.rightPath.getAngleVectors();
@@ -96,13 +125,28 @@ public class InputProcessor {
this.lastTime = System.currentTimeMillis();
}
/**
* Method that is called whenever the user performs a good repetition.
*/
public void onAdequateRepetition() {
ExerciseManager.TOTAL_REPETITIONS_PERFORMED++;
// TODO: Play sound
}
/**
* Method that is called whenever the user performs a bad repetition.
*/
public void onInadequateRepetition() {
}
/**
* Function for setting whether the motion data
* should be recorded or not.
*
* @param recording Whether the motion data should be recorded.
* @param duration For how long the motion data should be recorded.
* This only has an effect if `recording` is true.
* @param duration For how long the motion data should be recorded.
* This only has an effect if `recording` is true.
*/
public void setRecording(boolean recording, float duration) {
this.recordingMovement = recording;
@@ -171,7 +215,7 @@ public class InputProcessor {
try {
Log.i("MotionProcessor", "Time passed: " + this.secondsPassed + "s");
if ( this.recordingMovement)
if (this.recordingMovement)
Log.i("MotionProcessor", this.secondsPassed + " / " + this.recordingDurationInSeconds);
Log.i("MotionProcessor", "Received packet data: " + data);
@@ -218,7 +262,7 @@ public class InputProcessor {
// Supposed index of the current rotation vector in the `rotationVectorPaths` array
this.selfRotationVectorPaths[deviceId].add(rotation);
if ( this.recordingMovement && this.secondsPassed >= this.recordingDurationInSeconds) {
if (this.recordingMovement && this.secondsPassed >= this.recordingDurationInSeconds) {
// Do something with the recorded data.
this.recordingMovement = false;
// Convert recorded data from `selfRotationVectorPaths` to string, and
@@ -242,7 +286,21 @@ public class InputProcessor {
}
motionDataConsumer.accept(rotation, deviceId);
// Do something else with the vector
// TODO: Implement !!
Log.i("MotionProcessor", "Rotation vector: " + rotation.toString() + " from device: " + deviceId);
// Whenever the exercise has finished and it's not recording,
// attempt to move to the next exercise.
// If this fails, navigate back to the main activity.
if (this.hasFinished() && !this.recordingMovement) {
this.parentActivity.fetchExerciseAsync(this::nextExercise, (nil) -> {
Log.i("MotionProcessor", "Failed to fetch exercise data.");
NavigationManager.navigateToActivity(this.parentActivity, MainActivity.class);
});
}
}
}
@@ -253,8 +311,7 @@ public class InputProcessor {
*
* @return The converted string.
*/
private String convertRecordedDataToString()
{
private String convertRecordedDataToString() {
int[] intBits = new int[3];
char[] vectorChars = new char[12]; // 4 bytes per scalar, 12 chars per vector
JsonArray jsonArray = new JsonArray();
@@ -266,14 +323,14 @@ public class InputProcessor {
*/
// Iterate over all devices. In the current instance, it's 2.
for ( int deviceId = 0; deviceId < selfRotationVectorPaths.length; deviceId++) {
for (int deviceId = 0; deviceId < selfRotationVectorPaths.length; deviceId++) {
JsonObject jsonDeviceObject = new JsonObject();
jsonDeviceObject.addProperty("deviceId", deviceId);
// Data array
JsonArray jsonDeviceDataArray = new JsonArray();
for ( Vector3f vector : selfRotationVectorPaths[deviceId]) {
for (Vector3f vector : selfRotationVectorPaths[deviceId]) {
JsonArray jsonScalarArray = new JsonArray();
jsonScalarArray.add(vector.x);
jsonScalarArray.add(vector.y);
@@ -297,22 +354,12 @@ public class InputProcessor {
return secondsPassed / exerciseDurationInSeconds;
}
/**
* Function for setting the motion data receiver.
*
* @param consumer The consumer to set.
*/
public void setInputHandler(IInputHandler consumer) {
if (consumer != null)
this.motionDataConsumer = consumer;
}
/**
* Function for getting the combined (average) error value of both sensors.
public double getCombinedError()
{
public double getCombinedError()
{
}*/
}*/
/**
* Function for getting the error offsets of the user's path compared to the

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/midBlue" />
<stroke android:width="2dp" android:color="#FFFFFF" />
<corners android:radius="360dp" />
</shape>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/progress">
<rotate
android:fromDegrees="270"
android:toDegrees="270">
<shape android:shape="ring">
<gradient
android:startColor="#ADD8E6"
android:endColor="#798994"
android:angle="0"
android:type="sweep"/>
<size android:width="150dp"
android:height="150dp"/>
</shape>
</rotate>
</item>
</layer-list>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/progress">
<rotate
android:fromDegrees="270"
android:toDegrees="270">
<shape android:shape="ring">
<gradient
android:startColor="#FF0000"
android:endColor="#FF0000"
android:angle="0"
android:type="sweep"/>
<size android:width="150dp"
android:height="150dp"/>
</shape>
</rotate>
</item>
</layer-list>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/progress">
<rotate
android:fromDegrees="270"
android:toDegrees="270">
<shape android:shape="ring">
<gradient
android:startColor="#00FF00"
android:endColor="#00FF00"
android:angle="0"
android:type="sweep"/>
<size android:width="150dp"
android:height="150dp"/>
</shape>
</rotate>
</item>
</layer-list>

View File

@@ -97,44 +97,44 @@
android:textAlignment="center" />
</LinearLayout>
<LinearLayout
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/progressText"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:onClick="incrementProgress"
android:text="Increment" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="10dp"
android:background="@drawable/border_background_3"
android:padding="5dp">
android:background="@drawable/border_background_circle">
<ProgressBar
android:id="@+id/progressCircle"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:indeterminate="false"
android:max="10"
android:progress="10"
android:progressDrawable="@drawable/progress_circle" />
<TextView
android:id="@+id/textViewFitnessReps"
android:id="@+id/progressText"
style="@style/TextStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/context"
android:textAlignment="center" />
</LinearLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="0/10" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="10dp"
android:background="@drawable/border_background_3"
android:padding="5dp">
<TextView
android:id="@+id/textViewFitnessScore"
style="@style/TextStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/context"
android:textAlignment="center" />
</LinearLayout>
<com.example.fitbot.ui.components.ExerciseStatusElement
android:id="@+id/personalMotionPreviewElement"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
</LinearLayout>

View File

@@ -39,4 +39,8 @@
<item name="android:padding">6dp</item>
</style>
<style name="ProgressCircle" parent="Widget.AppCompat.ProgressBar">
<item name="android:indeterminateDrawable">@drawable/progress_circle</item>
</style>
</resources>

View File

@@ -10,9 +10,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
url 'https://qisdk.softbankrobotics.com/sdk/maven/'
}
maven { url 'https://qisdk.softbankrobotics.com/sdk/maven/' }
}
}
rootProject.name = "Fitbot"