Merge branch 'main' of https://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-4/muupooviixee66
This commit is contained in:
16
code/src/Fitbot/.idea/misc.xml
generated
16
code/src/Fitbot/.idea/misc.xml
generated
@@ -16,25 +16,33 @@
|
||||
<entry key="..\:/Users/Niels/muupooviixee66-3/code/src/Fitbot/app/src/main/res/layout/activity_end_screen.xml" value="0.165" />
|
||||
<entry key="..\:/Users/Niels/muupooviixee66-3/code/src/Fitbot/app/src/main/res/layout/activity_endscreen.xml" value="0.1" />
|
||||
<entry key="..\:/Users/Niels/muupooviixee66-3/code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml" value="0.1234375" />
|
||||
<entry key="..\:/Users/Niels/muupooviixee66-3/code/src/Fitbot/app/src/main/res/layout/activity_help.xml" value="0.1" />
|
||||
<entry key="..\:/Users/Niels/muupooviixee66-3/code/src/Fitbot/app/src/main/res/layout/activity_main.xml" value="0.1" />
|
||||
<entry key="..\:/Users/Niels/muupooviixee66-3/code/src/Fitbot/app/src/main/res/layout/header.xml" value="0.1234375" />
|
||||
<entry key="..\:/Users/Niels/muupooviixee66-3/code/src/Fitbot/app/src/main/res/layout/toolbar.xml" value="0.1234375" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.25" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/big_red_button_gradient.xml" value="0.2555" />
|
||||
<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/help2_background.xml" value="0.2395" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/help_background.xml" value="0.2395" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/ic_baseline_home_48.xml" value="0.25" />
|
||||
<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/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" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_main.xml" value="0.176" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_end_screen.xml" value="0.4" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml" value="0.6" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_help.xml" value="0.4" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_main.xml" value="0.4" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_main_screen.xml" value="0.1" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_power_screen.xml" value="0.1" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_sport_item.xml" value="0.1" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_sport_menu.xml" value="0.1" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/header.xml" value="0.125" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/toolbar.xml" value="0.125" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/header.xml" value="0.264" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/toolbar.xml" value="0.264" />
|
||||
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/menu/main_menu.xml" value="0.4" />
|
||||
<entry key="app/src/main/res/layout/activity_end_screen.xml" value="0.1" />
|
||||
<entry key="app/src/main/res/layout/activity_fitness.xml" value="0.23550724637681159" />
|
||||
<entry key="app/src/main/res/layout/activity_main.xml" value="0.1" />
|
||||
|
@@ -38,9 +38,6 @@ dependencies {
|
||||
implementation 'org.joml:joml:1.10.5'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||
implementation 'com.aldebaran:qisdk:1.7.5'
|
||||
|
@@ -3,21 +3,31 @@ package com.example.fitbot.exercise;
|
||||
public enum EMuscleGroup {
|
||||
// TODO: Implement
|
||||
|
||||
TORSO(0),
|
||||
ARMS(1),
|
||||
LEGS(2),
|
||||
BALANCE(3);
|
||||
TORSO(0, new String[]{"upper body", "torso"}),
|
||||
ARMS(1, new String[]{"arms", "arm", "shoulder"}),
|
||||
LEGS(2, new String[]{"Lower body", "legs", "leg"});
|
||||
|
||||
int muscleGroupIdentifier;
|
||||
String[] muscleGroupNames;
|
||||
|
||||
EMuscleGroup(int identifier) {
|
||||
EMuscleGroup(int identifier, String[] muscleGroupNames) {
|
||||
this.muscleGroupIdentifier = identifier;
|
||||
this.muscleGroupNames = muscleGroupNames;
|
||||
}
|
||||
|
||||
public int getIdentifier() {
|
||||
return this.muscleGroupIdentifier;
|
||||
}
|
||||
|
||||
public static EMuscleGroup parse(String name)
|
||||
{
|
||||
for ( EMuscleGroup muscleGroup : EMuscleGroup.values())
|
||||
for ( String muscleGroupName : muscleGroup.muscleGroupNames)
|
||||
if ( muscleGroupName.equalsIgnoreCase(name))
|
||||
return muscleGroup;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static EMuscleGroup parse(int identifier) {
|
||||
for (EMuscleGroup muscleGroup : EMuscleGroup.values()) {
|
||||
if (muscleGroup.getIdentifier() == identifier) {
|
||||
|
@@ -3,26 +3,21 @@ package com.example.fitbot.exercise;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.fitbot.util.path.GesturePath;
|
||||
import com.example.fitbot.util.processing.IMotionDataConsumer;
|
||||
import com.example.fitbot.util.server.IWebServerHandler;
|
||||
import com.example.fitbot.util.server.WebServer;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class Exercise implements IWebServerHandler {
|
||||
|
||||
private EMuscleGroup muscleGroup;
|
||||
private GesturePath leftPath;
|
||||
private GesturePath rightPath;
|
||||
private String title;
|
||||
private String description;
|
||||
private float segmentsPerSecond;
|
||||
|
||||
// Static fields.
|
||||
private static WebServer webSocket;
|
||||
private static Exercise currentExercise = null;
|
||||
public class Exercise {
|
||||
|
||||
public final EMuscleGroup muscleGroup;
|
||||
public final GesturePath leftPath;
|
||||
public final GesturePath rightPath;
|
||||
public final String title;
|
||||
public final String description;
|
||||
public final String imageUrl;
|
||||
public final String videoUrl;
|
||||
public final float exerciseTimeInSeconds;
|
||||
|
||||
/**
|
||||
* Constructor for the AbstractExercise class.
|
||||
@@ -32,106 +27,17 @@ public class Exercise implements IWebServerHandler {
|
||||
* @param rightPath The path of the right hand.
|
||||
* @param title The title of the exercise.
|
||||
* @param description The description of the exercise.
|
||||
* @param segmentsPerSecond The number of segments per second.
|
||||
* This determines how fast the exercise should be performed.
|
||||
* @param imageUrl The URL of the image.
|
||||
* @param videoUrl The URL of the video.
|
||||
*/
|
||||
public Exercise(EMuscleGroup muscleGroup, String title, String description, GesturePath leftPath, GesturePath rightPath) {
|
||||
public Exercise(EMuscleGroup muscleGroup, String title, String description, String imageUrl, String videoUrl, GesturePath leftPath, GesturePath rightPath, float exerciseTimeInSeconds) {
|
||||
this.muscleGroup = muscleGroup;
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.leftPath = leftPath;
|
||||
this.rightPath = rightPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the exercise.
|
||||
* This method starts a WebSocket server
|
||||
*/
|
||||
public final void startExercise() {
|
||||
|
||||
// Ensure no other exercise is active.
|
||||
if (currentExercise != null && currentExercise != this) {
|
||||
currentExercise.__stopExercise();
|
||||
Log.i("Exercises", "Another exercise was started when another was still running.");
|
||||
}
|
||||
|
||||
// If a WebSocket server is already running, change the event handler to be this class.
|
||||
if (webSocket != null && webSocket.isConnected()) {
|
||||
webSocket.setEventHandler(this);
|
||||
}
|
||||
|
||||
try {
|
||||
webSocket = WebServer.createServer();
|
||||
Objects.requireNonNull(webSocket, "WebSocket server could not be created.");
|
||||
|
||||
webSocket.setEventHandler(this);
|
||||
currentExercise = this;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for ending this exercise and returning the grade of the performance
|
||||
* of this activity.
|
||||
*/
|
||||
public final double finishExercise() {
|
||||
this.__stopExercise();
|
||||
|
||||
// TODO: Implement grade calculation
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the exercise.
|
||||
* This method stops the WebSocket server.
|
||||
*/
|
||||
private void __stopExercise() {
|
||||
if (webSocket != null && webSocket.isConnected()) {
|
||||
webSocket.stop();
|
||||
webSocket = null;
|
||||
}
|
||||
currentExercise = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current exercise is the current activity.
|
||||
*/
|
||||
public final boolean isCurrentActivity() {
|
||||
return currentExercise == this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the muscle group of the exercise.
|
||||
*/
|
||||
public EMuscleGroup getMuscleGroup() {
|
||||
return muscleGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path of the exercise.
|
||||
*/
|
||||
public GesturePath[] getPath() {
|
||||
return new GesturePath[]{leftPath, rightPath};
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the speed of the exercise.
|
||||
*/
|
||||
public double getSegmentsPerSecond() {
|
||||
return segmentsPerSecond;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(String message) {
|
||||
|
||||
this.imageUrl = imageUrl;
|
||||
this.videoUrl = videoUrl;
|
||||
this.exerciseTimeInSeconds = exerciseTimeInSeconds;
|
||||
}
|
||||
}
|
||||
|
@@ -4,25 +4,35 @@ import com.example.fitbot.util.path.GesturePath;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
public class ExerciseManager {
|
||||
|
||||
private static final String HOST_ADDRESS = "http://145.92.8.132";
|
||||
private static final String HOST_ADDRESS = "http://145.92.8.132:443/";
|
||||
|
||||
// The value of these property variables must be equivalent of
|
||||
// the JSON data that the database sends back.
|
||||
// If this is not the case then the exercise retrieval will fail.
|
||||
private static final String PROPERTY_MUSCLE_GROUP = "muscleGroup";
|
||||
private static final String PROPERTY_DESC = "description";
|
||||
private static final String PROPERTY_VECTORS = "vector_data";
|
||||
private static final String PROPERTY_IMAGE_URL = "imageUrl";
|
||||
private static final String PROPERTY_VIDEO_URL = "videoUrl";
|
||||
private static final String PROPERTY_NAME = "name";
|
||||
private static final String PROPERTY_MUSCLE_GROUP = "muscle_group";
|
||||
private static final String PROPERTY_SEGMENT_SPEED = "segment_speed";
|
||||
private static final String PROPERTY_DATA = "data";
|
||||
private static final String PROPERTY_EXERCISE_DURATION = "exerciseDuration";
|
||||
|
||||
public static final int SENSOR_COUNT = 2;
|
||||
|
||||
private static final String[] REQUIRED_PROPERTIES = {
|
||||
PROPERTY_MUSCLE_GROUP, PROPERTY_DESC, PROPERTY_IMAGE_URL,
|
||||
PROPERTY_VIDEO_URL, PROPERTY_NAME, PROPERTY_DATA,
|
||||
PROPERTY_EXERCISE_DURATION
|
||||
};
|
||||
|
||||
private static final float DEFAULT_SEGMENT_SPEED = 1.0f;
|
||||
|
||||
@@ -36,13 +46,17 @@ public class ExerciseManager {
|
||||
*
|
||||
* @return The response from the server.
|
||||
*/
|
||||
private static String sendHTTP(String url, String method, String contentType, String body) {
|
||||
public static String sendHTTP(String url, String method, String contentType, String body) {
|
||||
try {
|
||||
URLConnection connection = new URL(url).openConnection();
|
||||
connection.setDoOutput(true);
|
||||
connection.setDoInput(true);
|
||||
connection.addRequestProperty("Content-Type", contentType);
|
||||
connection.addRequestProperty("Request-Method", method);
|
||||
connection.getOutputStream().write(body.getBytes());
|
||||
connection.connect();
|
||||
// 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();
|
||||
@@ -60,23 +74,41 @@ public class ExerciseManager {
|
||||
/**
|
||||
* Function for retrieving an exercise from the Raspberry Pi Database.
|
||||
*
|
||||
* @param uniqueIdentifier The unique identifier of the exercise
|
||||
* @return The exercise, if it exists on the server. Otherwise null.
|
||||
*/
|
||||
public static Exercise retrieveExercise(String uniqueIdentifier) {
|
||||
public static Exercise retrieveExercise() {
|
||||
String response = sendHTTP(
|
||||
HOST_ADDRESS + "/acquire", "GET", "application/json", "{\"kind\":\"" + uniqueIdentifier + "\"}"
|
||||
HOST_ADDRESS, "POST", "application/json", null
|
||||
);
|
||||
// Validate the response
|
||||
if (response != null) {
|
||||
try {
|
||||
JsonObject content = JsonParser.parseString(response).getAsJsonObject();
|
||||
|
||||
// Ensure all required properties are present
|
||||
for (String property : REQUIRED_PROPERTIES) {
|
||||
if (!content.has(property)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Path data is split into two parts, due to the left and right hand.
|
||||
// If one wants to add support for more sensors, one will have to adjust the Exercise
|
||||
// class to support more paths.
|
||||
String[] leftRightData = content.get(PROPERTY_DATA).getAsString().split(";");
|
||||
|
||||
if ( leftRightData.length != SENSOR_COUNT)
|
||||
return null;
|
||||
|
||||
return new Exercise(
|
||||
EMuscleGroup.parse(content.get(PROPERTY_MUSCLE_GROUP).getAsInt()),
|
||||
EMuscleGroup.parse(content.get(PROPERTY_MUSCLE_GROUP).getAsString()),
|
||||
content.get(PROPERTY_NAME).getAsString(),
|
||||
content.get(PROPERTY_DESC).getAsString(),
|
||||
gesturePathFromString(content.get(PROPERTY_VECTORS).getAsString()),
|
||||
gesturePathFromString(content.get(PROPERTY_SEGMENT_SPEED).getAsString())
|
||||
content.get(PROPERTY_IMAGE_URL).getAsString(),
|
||||
content.get(PROPERTY_VIDEO_URL).getAsString(),
|
||||
GesturePath.fromString(leftRightData[0]),
|
||||
GesturePath.fromString(leftRightData[1]),
|
||||
DEFAULT_SEGMENT_SPEED
|
||||
);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -84,42 +116,4 @@ public class ExerciseManager {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for converting a string to a GesturePath object.
|
||||
* The input string bytes will be directly converted into 3d vectors.
|
||||
* Every scalar is composed of 32 bits (4 characters), meaning 96 bits per vector.
|
||||
*
|
||||
* Note: ASCII to Vector conversion is done in Big Endian format (most significant byte first).
|
||||
*
|
||||
* @param input The string to convert
|
||||
* @return The GesturePath object
|
||||
*/
|
||||
private static GesturePath gesturePathFromString(String input) {
|
||||
byte[] bytes = input.getBytes();
|
||||
|
||||
// Check if the input string contains a valid amount of bytes (12 bytes per vector)
|
||||
if (input.length() % 12 != 0) {
|
||||
throw new IllegalArgumentException("Invalid input string length");
|
||||
}
|
||||
GesturePath.Builder builder = new GesturePath.Builder();
|
||||
|
||||
float[] xyz = new float[3];
|
||||
for (int i = 0; i < bytes.length; i += 12) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
|
||||
xyz[j] = Float.intBitsToFloat(
|
||||
(bytes[i + j * 4] & 0xFF) << 24 |
|
||||
(bytes[i + j * 4 + 1] & 0xFF) << 16 |
|
||||
(bytes[i + j * 4 + 2] & 0xFF) << 8 |
|
||||
(bytes[i + j * 4 + 3] & 0xFF)
|
||||
);
|
||||
}
|
||||
builder.addVector(new Vector3f(
|
||||
xyz[0], xyz[1], xyz[2]
|
||||
));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ public class EndScreenActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_end_screen);
|
||||
|
||||
com.example.fitbot.util.ButtonNavigation.setupButtonNavigation(this, R.id.homeButton, MainActivity.class);
|
||||
com.example.fitbot.util.ButtonNavigation.setupButtonNavigation(this, R.id.continueButton, FitnessActivity.class);
|
||||
com.example.fitbot.util.ButtonNavigation.setupButtonNavigation(this, R.id.homeButtonEndScreen, MainActivity.class);
|
||||
com.example.fitbot.util.ButtonNavigation.setupButtonNavigation(this, R.id.startButtonEndScreen, FitnessActivity.class);
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@ package com.example.fitbot.ui.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.VideoView;
|
||||
|
||||
import com.aldebaran.qi.sdk.QiContext;
|
||||
@@ -10,25 +11,43 @@ import com.aldebaran.qi.sdk.RobotLifecycleCallbacks;
|
||||
import com.aldebaran.qi.sdk.design.activity.RobotActivity;
|
||||
import com.aldebaran.qi.sdk.design.activity.conversationstatus.SpeechBarDisplayStrategy;
|
||||
import com.example.fitbot.R;
|
||||
import com.example.fitbot.exercise.EMuscleGroup;
|
||||
import com.example.fitbot.exercise.Exercise;
|
||||
import com.example.fitbot.exercise.ExerciseManager;
|
||||
import com.example.fitbot.ui.components.PersonalMotionPreviewElement;
|
||||
import com.example.fitbot.util.ButtonNavigation;
|
||||
import com.example.fitbot.util.FitnessCycle;
|
||||
import com.example.fitbot.util.path.GesturePath;
|
||||
import com.example.fitbot.util.processing.InputProcessor;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class FitnessActivity extends RobotActivity implements RobotLifecycleCallbacks {
|
||||
|
||||
PersonalMotionPreviewElement personalMotionPreviewElement;
|
||||
// Private fields for the FitnessActivity class.
|
||||
private PersonalMotionPreviewElement personalMotionPreviewElement;
|
||||
private InputProcessor motionProcessor;
|
||||
private Exercise currentExercise;
|
||||
|
||||
private QiContext qiContext;
|
||||
|
||||
// Some nice little messages for the user
|
||||
private static final String[] EXERCISE_NOT_FOUND_MESSAGES = new String[] {
|
||||
"Ik heb momenteel helaas wat moeite met het ophalen van oefeningen, sorry.",
|
||||
"Het lijkt erop dat de oefeningen op een misterieus avontuur zijn. Even wachten tot ze terug zijn.",
|
||||
"Ssst, de oefeningen slapen nog, probeer het later nog eens."
|
||||
};
|
||||
|
||||
private static final String EXERCISE_NOT_FOUND_SEEK_HELP_MESSAGE =
|
||||
"Indien dit probleem zich voortzet, neem contact op met de ontwikkelaar.";
|
||||
|
||||
private static final float SENSOR_SAMPLE_RATE = 10.0f;
|
||||
private static final int EXERCISE_COUNT = 5;
|
||||
private static final float EXERCISE_SPEED_MULTIPLIER = 1.0f;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
QiSDK.register(this, this);
|
||||
|
||||
setContentView(R.layout.activity_fitness);
|
||||
|
||||
// Remove the ugly ass bar on top of the view
|
||||
@@ -36,50 +55,62 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
|
||||
|
||||
// Find the VideoView by its ID
|
||||
VideoView videoView = findViewById(R.id.videoView);
|
||||
|
||||
FitnessCycle.playVideo(videoView, this);
|
||||
|
||||
ButtonNavigation.setupButtonNavigation(this, R.id.homeButton, MainActivity.class);
|
||||
ButtonNavigation.setupButtonNavigation(this, R.id.buttonComplete, EndScreenActivity.class);
|
||||
ButtonNavigation.setupButtonNavigation(this, R.id.homeButtonFitness, MainActivity.class);
|
||||
// Implement your logic when the robot focus is gained
|
||||
|
||||
GesturePath.Builder gesturePathBuilder = new GesturePath.Builder();
|
||||
|
||||
/* Generate a random path to test the tracking system */
|
||||
for ( int i = 0; i < 40; i++)
|
||||
{
|
||||
gesturePathBuilder.addVector(
|
||||
new Vector3f(
|
||||
(float)Math.cos(Math.PI + (Math.PI / 40.0f) * i),
|
||||
(float)Math.sin(Math.PI + (Math.PI / 40.0f) * i),
|
||||
0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
personalMotionPreviewElement = findViewById(R.id.personalMotionPreviewElement);
|
||||
personalMotionPreviewElement.post(() -> {
|
||||
Log.i("FitnessActivity", "PersonalMotionPreviewElement.post()");
|
||||
|
||||
Exercise exercise = new Exercise(EMuscleGroup.ARMS, "Bicep Curls", "Oefening voor de biceps.", gesturePathBuilder.build(), gesturePathBuilder.build());
|
||||
|
||||
personalMotionPreviewElement.initialize(exercise);
|
||||
personalMotionPreviewElement.provideQiContext(null);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRobotFocusGained(QiContext qiContext) {
|
||||
|
||||
this.qiContext = qiContext;
|
||||
// Find the VideoView by its ID
|
||||
// CompletableFuture.runAsync(() -> FitnessCycle.executeMovement("bicepcurl", 10, qiContext));
|
||||
Log.i("Motion", "qiContext provided");
|
||||
|
||||
personalMotionPreviewElement = 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.
|
||||
personalMotionPreviewElement.post(() -> {
|
||||
Exercise exercise = this.acquireExercise();
|
||||
if ( exercise == null) {
|
||||
return;
|
||||
}
|
||||
// Acquire paths from the exercise and provide them to the motion processor
|
||||
Vector3f[][] vectors = new Vector3f[][] {exercise.leftPath.getVectors(), exercise.rightPath.getVectors()};
|
||||
|
||||
motionProcessor = new InputProcessor(vectors, exercise.exerciseTimeInSeconds, SENSOR_SAMPLE_RATE);
|
||||
|
||||
personalMotionPreviewElement.provideQiContext(qiContext);
|
||||
personalMotionPreviewElement.initialize(exercise, motionProcessor, EXERCISE_COUNT);
|
||||
|
||||
motionProcessor.startListening();
|
||||
motionProcessor.setInputHandler(personalMotionPreviewElement);
|
||||
});
|
||||
personalMotionPreviewElement.provideQiContext(qiContext);
|
||||
|
||||
// FitnessCycle.playVideo(qiContext, videoView, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire an exercise from the ExerciseManager.
|
||||
* Whenever the retrieval failed, it will have the robot say something to the user
|
||||
* to inform them about the issue.
|
||||
*
|
||||
* @return The acquired exercise, or null if the exercise could not be retrieved.
|
||||
*/
|
||||
public Exercise acquireExercise() {
|
||||
Exercise exercise = ExerciseManager.retrieveExercise();
|
||||
if ( exercise == null && this.qiContext != null)
|
||||
{
|
||||
int randomMessageIndex = (int)Math.floor(Math.random() * EXERCISE_NOT_FOUND_MESSAGES.length);
|
||||
FitnessCycle.say(EXERCISE_NOT_FOUND_MESSAGES[randomMessageIndex], qiContext);
|
||||
FitnessCycle.say(EXERCISE_NOT_FOUND_SEEK_HELP_MESSAGE, qiContext);
|
||||
}
|
||||
return exercise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRobotFocusLost() {
|
||||
// Implement your logic when the robot focus is lost
|
||||
@@ -94,6 +125,8 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
QiSDK.unregister(this, this);
|
||||
this.personalMotionPreviewElement.onDestroy();
|
||||
this.motionProcessor.stopListening();
|
||||
this.motionProcessor = null;
|
||||
this.personalMotionPreviewElement.destroy();
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@ package com.example.fitbot.ui.activities;
|
||||
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import com.example.fitbot.R;
|
||||
import com.example.fitbot.util.ButtonNavigation;
|
||||
@@ -13,7 +14,18 @@ public class HelpActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_help);
|
||||
|
||||
ButtonNavigation.setupButtonNavigation(this, R.id.homeButton, MainActivity.class);
|
||||
ButtonNavigation.setupButtonNavigation(this, R.id.homeButtonHelp, MainActivity.class);
|
||||
|
||||
// Hide system UI
|
||||
hideSystemUI();
|
||||
}
|
||||
|
||||
private void hideSystemUI() {
|
||||
View decorView = getWindow().getDecorView();
|
||||
// Hide the status bar and navigation bar
|
||||
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
decorView.setSystemUiVisibility(uiOptions);
|
||||
}
|
||||
}
|
@@ -11,6 +11,8 @@ import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.example.fitbot.R;
|
||||
@@ -18,20 +20,24 @@ import com.example.fitbot.util.ButtonNavigation;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
//Variables
|
||||
// Variables
|
||||
DrawerLayout drawerLayout;
|
||||
NavigationView navigationView;
|
||||
Toolbar toolbar;
|
||||
Button startButton;
|
||||
|
||||
@SuppressLint("WrongViewCast")
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
Button startButton = findViewById(R.id.startButton);
|
||||
// Set full screen mode to hide status bar
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
|
||||
|
||||
startButton = findViewById(R.id.startButtonMain);
|
||||
startButton.setOnClickListener(v -> {
|
||||
Uri videoUri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.bicepvideo);
|
||||
Intent intent = new Intent(MainActivity.this, FitnessActivity.class);
|
||||
@@ -39,7 +45,11 @@ public class MainActivity extends AppCompatActivity {
|
||||
startActivity(intent);
|
||||
});
|
||||
|
||||
setUpUi(); // Set up the UI
|
||||
// Set up the UI
|
||||
setUpUi();
|
||||
|
||||
// Hide system UI
|
||||
hideSystemUI();
|
||||
}
|
||||
|
||||
private void setUpUi() {
|
||||
@@ -47,31 +57,65 @@ public class MainActivity extends AppCompatActivity {
|
||||
drawerLayout = findViewById(R.id.drawer_layout);
|
||||
navigationView = findViewById(R.id.nav_view);
|
||||
toolbar = findViewById(R.id.toolbar);
|
||||
startButton = findViewById(R.id.startButton);
|
||||
startButton = findViewById(R.id.startButtonMain);
|
||||
|
||||
ButtonNavigation.setupButtonNavigation(this, R.id.startButton, FitnessActivity.class);
|
||||
ButtonNavigation.setupButtonNavigation(this, R.id.helpButton, HelpActivity.class);
|
||||
// Hide the action bar
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
ButtonNavigation.setupButtonNavigation(this, R.id.startButtonMain, FitnessActivity.class);
|
||||
ButtonNavigation.setupButtonNavigation(this, R.id.helpButtonMain, HelpActivity.class);
|
||||
|
||||
/*---Tool Bar---*/
|
||||
setSupportActionBar(toolbar); // Make the toolbar act as the action bar
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false); // Remove the title from the toolbar
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false); // Remove the title from the toolbar
|
||||
}
|
||||
|
||||
/*---Navigation Drawer Menu---*/
|
||||
navigationView.bringToFront(); // Make the navigation drawer menu clickable
|
||||
|
||||
ActionBarDrawerToggle toggle=new // Create a toggle for the navigation drawer
|
||||
ActionBarDrawerToggle(this,drawerLayout,toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
|
||||
ActionBarDrawerToggle toggle = new // Create a toggle for the navigation drawer
|
||||
ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) {
|
||||
@Override
|
||||
public void onDrawerOpened(View drawerView) {
|
||||
super.onDrawerOpened(drawerView);
|
||||
hideSystemUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawerClosed(View drawerView) {
|
||||
super.onDrawerClosed(drawerView);
|
||||
hideSystemUI();
|
||||
}
|
||||
};
|
||||
drawerLayout.addDrawerListener(toggle);
|
||||
toggle.syncState(); // Synchronize the state of the navigation drawer
|
||||
}
|
||||
|
||||
private void hideSystemUI() {
|
||||
View decorView = getWindow().getDecorView();
|
||||
// Hide the status bar and navigation bar
|
||||
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
decorView.setSystemUiVisibility(uiOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed(){ // Close the navigation drawer when the back button is pressed
|
||||
if(drawerLayout.isDrawerOpen(GravityCompat.START)){
|
||||
drawerLayout.closeDrawer(GravityCompat.START);
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (hasFocus) {
|
||||
hideSystemUI();
|
||||
}
|
||||
else
|
||||
{super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() { // Close the navigation drawer when the back button is pressed
|
||||
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
||||
drawerLayout.closeDrawer(GravityCompat.START);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,18 +1,23 @@
|
||||
package com.example.fitbot.ui.components;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.aldebaran.qi.sdk.QiContext;
|
||||
import com.example.fitbot.exercise.Exercise;
|
||||
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.FitnessCycle;
|
||||
import com.example.fitbot.util.path.GesturePath;
|
||||
import com.example.fitbot.util.processing.MotionProcessor;
|
||||
import com.example.fitbot.util.processing.IInputHandler;
|
||||
import com.example.fitbot.util.processing.InputProcessor;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector2f;
|
||||
@@ -20,61 +25,58 @@ import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class PersonalMotionPreviewElement extends View {
|
||||
|
||||
private GesturePath[] paths;
|
||||
private MotionProcessor motionProcessor;
|
||||
|
||||
private double pathTime = 0.0D; // The timestamp at which the path is currently at.
|
||||
private final AtomicInteger exerciseProgress = new AtomicInteger(0); // The progress of the exercise. Ranges from 0 to 1000.
|
||||
|
||||
private QiContext qiContext;
|
||||
public class PersonalMotionPreviewElement extends View implements IInputHandler {
|
||||
|
||||
// Fields regarding Exercise and speech handling.
|
||||
private InputProcessor motionProcessor;
|
||||
private Exercise exercise;
|
||||
private QiContext qiContext;
|
||||
private int exerciseCount;
|
||||
|
||||
private Path targetPath; // The path the user is supposed to follow.
|
||||
private Path actualPath; // The path the user is currently following.
|
||||
private FitnessActivity parentActivity;
|
||||
|
||||
private final Paint referencePaint = new Paint();
|
||||
private final Paint targetPaint = new Paint();
|
||||
private final Paint backgroundColor = new Paint();
|
||||
private final Paint userProgressPaint = new Paint();
|
||||
private final Paint borderPaint = new Paint();
|
||||
private final Paint backgroundPaint = new Paint();
|
||||
|
||||
// TODO: Remove
|
||||
private Matrix4f viewMatrix = new Matrix4f(); // The view matrix for the 3D to 2D transformation.
|
||||
private Matrix4f projectionMatrix = new Matrix4f(); // The projection matrix for the 3D to 2D transformation.
|
||||
private final Vector4f objectPosition = new Vector4f(0, 0, 0, 1); // The location of the object in 3D space.
|
||||
private ConcurrentLinkedQueue<Vector2f> vectors = new ConcurrentLinkedQueue<>();
|
||||
|
||||
// TODO: Remove
|
||||
private Vector2f[] axisVectors = new Vector2f[0];
|
||||
|
||||
|
||||
|
||||
private static final String[] USER_PHRASES = {
|
||||
private static final String[] STARTING_PHRASES = {
|
||||
"Veel success met de oefening!",
|
||||
"Je kan het!",
|
||||
"Veel plezier!"
|
||||
};
|
||||
|
||||
private double timePassed = 0.0D; // The time that has passed since the start of the exercise, in seconds.
|
||||
private long startingTime = 0L;
|
||||
|
||||
private Vector2f screenDimensions = new Vector2f(); // Width and height dimensions of the screen
|
||||
|
||||
public PersonalMotionPreviewElement(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
this.referencePaint.setColor(0xFFFF0000); // Red
|
||||
this.referencePaint.setStyle(Paint.Style.FILL);
|
||||
this.referencePaint.setStrokeWidth(5.0f);
|
||||
this.referencePaint.setAntiAlias(true);
|
||||
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.targetPaint.setColor(-1);
|
||||
this.targetPaint.setStyle(Paint.Style.STROKE);
|
||||
this.targetPaint.setStrokeWidth(5.0f);
|
||||
this.targetPaint.setAntiAlias(true);
|
||||
this.borderPaint.setColor(-1);
|
||||
this.borderPaint.setStyle(Paint.Style.STROKE);
|
||||
this.borderPaint.setStrokeWidth(5.0f);
|
||||
this.borderPaint.setAntiAlias(true);
|
||||
|
||||
this.backgroundPaint.setColor(0xFF000000); // Black
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,34 +86,28 @@ public class PersonalMotionPreviewElement extends View {
|
||||
* 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(Exercise exercise) {
|
||||
public void initialize(@Nullable Exercise exercise, InputProcessor motionProcessor, int exerciseCount) {
|
||||
Log.i("PersonalMotionPreviewElement", "Creating new PersonalMotionPreviewElement.");
|
||||
this.backgroundColor.setColor(0xFF000000); // Black
|
||||
|
||||
this.screenDimensions.x = this.getWidth();
|
||||
this.screenDimensions.y = this.getHeight();
|
||||
this.actualPath = new Path();
|
||||
this.targetPath = new Path();
|
||||
|
||||
this.startingTime = System.nanoTime(); // Set the last time to the current time
|
||||
|
||||
this.motionProcessor = motionProcessor;
|
||||
this.exercise = exercise;
|
||||
this.paths = exercise.getPath();
|
||||
this.exerciseCount = exerciseCount;
|
||||
|
||||
// TODO: Remove
|
||||
this.axisVectors = new Vector2f[] {
|
||||
|
||||
projectVertex(new Vector3f(-100.0f, 0, 0), getWidth(), getHeight()),
|
||||
projectVertex(new Vector3f(100.0f, 0, 0), getWidth(), getHeight()),
|
||||
projectVertex(new Vector3f(0, -100.0f, 0), getWidth(), getHeight()),
|
||||
projectVertex(new Vector3f(0, 100.0f, 0), getWidth(), getHeight()),
|
||||
projectVertex(new Vector3f(0, 0, -100.0f), getWidth(), getHeight()),
|
||||
projectVertex(new Vector3f(0, 0, 100.0f), getWidth(), getHeight())
|
||||
|
||||
projectVertex(new Vector3f(-5.0f, 0, 0), getWidth(), getHeight()),
|
||||
projectVertex(new Vector3f(5.0f, 0, 0), getWidth(), getHeight()),
|
||||
projectVertex(new Vector3f(0, -5.0f, 0), getWidth(), getHeight()),
|
||||
projectVertex(new Vector3f(0, 5.0f, 0), getWidth(), getHeight()),
|
||||
projectVertex(new Vector3f(0, 0, -5.0f), getWidth(), getHeight()),
|
||||
projectVertex(new Vector3f(0, 0, 5.0f), getWidth(), getHeight())
|
||||
};
|
||||
}
|
||||
|
||||
public void onDestroy()
|
||||
public void destroy()
|
||||
{
|
||||
if ( this.motionProcessor != null )
|
||||
this.motionProcessor.stopListening();
|
||||
@@ -128,35 +124,55 @@ public class PersonalMotionPreviewElement extends View {
|
||||
*/
|
||||
public void provideQiContext(QiContext context) {
|
||||
this.qiContext = context;
|
||||
if ( this.motionProcessor != null )
|
||||
this.motionProcessor.stopListening();
|
||||
|
||||
this.motionProcessor = new MotionProcessor();
|
||||
this.motionProcessor.startListening();
|
||||
|
||||
// Handler that is called every time the motion processor receives new data.
|
||||
this.motionProcessor.setMotionDataEventHandler((processed, preprocessed, sampleIndex, sampleRate, deviceId) -> {
|
||||
int progress = (int)this.motionProcessor.getError(this.paths[0], processed);
|
||||
this.exerciseProgress.set(Math.min(1000, Math.max(0, progress)));
|
||||
Log.i("MotionProcessor", "Processed data: " + progress + " (" + preprocessed + ")");
|
||||
Vector2f parsed = projectVertex(processed, this.getWidth(), this.getHeight());
|
||||
this.motionProcessor.setInputHandler((rotationVector, deviceId) -> {
|
||||
|
||||
Log.i("MotionProcessor", "Rotation vector received: " + rotationVector);
|
||||
Log.i("MotionProcessor", "Last error offset:" + this.motionProcessor.getError(deviceId, this.motionProcessor.secondsPassed()));
|
||||
|
||||
if ( this.motionProcessor.hasFinished())
|
||||
{
|
||||
if ( this.parentActivity == null)
|
||||
{
|
||||
// Move to main screen
|
||||
this.destroy();
|
||||
Intent intent = new Intent(getContext(), MainActivity.class);
|
||||
getContext().startActivity(intent);
|
||||
return;
|
||||
}
|
||||
// Move on to the next exercise, or finish.
|
||||
if ( this.exerciseCount > 0 )
|
||||
{
|
||||
this.exerciseCount--;
|
||||
this.exercise = this.parentActivity.acquireExercise();
|
||||
this.motionProcessor.useExercise(this.exercise);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Finish the exercise.
|
||||
this.destroy();
|
||||
Intent intent = new Intent(getContext(), EndScreenActivity.class);
|
||||
getContext().startActivity(intent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Adjust / remove
|
||||
vectors.add(projectVertex(rotationVector, this.getWidth(), this.getHeight()));
|
||||
Log.i("MotionProcessor", "Rotation vector received: " + rotationVector);
|
||||
Vector2f parsed = projectVertex(rotationVector, this.getWidth(), this.getHeight());
|
||||
|
||||
this.vectors.add(parsed);
|
||||
// Remove the first element if the array is too big
|
||||
if (this.vectors.size() > 100)
|
||||
this.vectors.poll();
|
||||
});
|
||||
saySomethingNice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to say something nice to the user :)
|
||||
*/
|
||||
private void saySomethingNice()
|
||||
{
|
||||
if (this.qiContext == null)
|
||||
return;
|
||||
|
||||
FitnessCycle.say(USER_PHRASES[(int) Math.floor(Math.random() * USER_PHRASES.length)], this.qiContext);
|
||||
FitnessCycle.say(STARTING_PHRASES[(int) Math.floor(Math.random() * STARTING_PHRASES.length)], this.qiContext);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,6 +181,7 @@ public class PersonalMotionPreviewElement extends View {
|
||||
* @param exercise The exercise that the user is currently performing.
|
||||
*/
|
||||
public void setExercise(Exercise exercise) {
|
||||
this.motionProcessor.useExercise(exercise);
|
||||
this.exercise = exercise;
|
||||
}
|
||||
|
||||
@@ -178,12 +195,12 @@ public class PersonalMotionPreviewElement extends View {
|
||||
// Perspective transformation conserves the depth of the object
|
||||
projectionMatrix
|
||||
.identity()
|
||||
.perspective((float) Math.toRadians(70), (float) virtualWidth / virtualHeight, .01f, 10000.0f);
|
||||
.perspective((float) Math.toRadians(70), (float) virtualWidth / virtualHeight, .01f, 1000.0f);
|
||||
|
||||
// Convert world coordinates to screen-space using MVP matrix
|
||||
Vector4f screenCoordinates = new Vector4f(point, 1.0f)
|
||||
.mul(this.projectionMatrix)
|
||||
.mul(this.viewMatrix);
|
||||
.mul(this.viewMatrix)
|
||||
.mul(this.projectionMatrix);
|
||||
|
||||
// Normalize screen coordinates from (-1, 1) to (0, virtualWidth) and (0, virtualHeight)
|
||||
float normalizedX = (screenCoordinates.x / screenCoordinates.w + 1.0f) * 0.5f * virtualWidth;
|
||||
@@ -195,23 +212,23 @@ public class PersonalMotionPreviewElement extends View {
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundColor);
|
||||
canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundPaint);
|
||||
this.setBackgroundColor(0xFF000000); // Black
|
||||
/*if (this.exercise == null)
|
||||
return;*/
|
||||
|
||||
for (int i = 0, startX, endX, startY, endY; i < axisVectors.length/2; i++)
|
||||
{
|
||||
startX = (int)Math.max(0, Math.min(this.screenDimensions.x, (int)axisVectors[i*2].x));
|
||||
endX = (int)Math.max(0, Math.min(this.screenDimensions.x, (int)axisVectors[i*2+1].x));
|
||||
startY = (int)Math.max(0, Math.min(this.screenDimensions.y, (int)axisVectors[i*2].y));
|
||||
endY = (int)Math.max(0, Math.min(this.screenDimensions.y, (int)axisVectors[i*2+1].y));
|
||||
canvas.drawLine(startX, startY, endX, endY, this.targetPaint);
|
||||
startX = (int)Math.max(0, Math.min(this.getWidth(), (int)axisVectors[i*2].x));
|
||||
endX = (int)Math.max(0, Math.min(this.getWidth(), (int)axisVectors[i*2+1].x));
|
||||
startY = (int)Math.max(0, Math.min(this.getHeight(), (int)axisVectors[i*2].y));
|
||||
endY = (int)Math.max(0, Math.min(this.getHeight(), (int)axisVectors[i*2+1].y));
|
||||
canvas.drawLine(startX, startY, endX, endY, this.borderPaint);
|
||||
}
|
||||
|
||||
for ( Vector2f point : this.vectors)
|
||||
{
|
||||
canvas.drawRect(point.x, point.y, point.x + 5, point.y + 5, this.referencePaint);
|
||||
canvas.drawRect(point.x, point.y, point.x + 5, point.y + 5, this.userProgressPaint);
|
||||
}
|
||||
/*
|
||||
// Draw target circle
|
||||
@@ -228,7 +245,10 @@ public class PersonalMotionPreviewElement extends View {
|
||||
);*/
|
||||
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Vector3f rotationVector, int sensorId) {
|
||||
|
||||
timePassed = (System.nanoTime() - startingTime) / 1E9D;
|
||||
}
|
||||
}
|
||||
|
@@ -43,6 +43,19 @@ public class GesturePath {
|
||||
return segments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for retrieving the vectors of the GesturePath.
|
||||
*/
|
||||
public Vector3f[] getVectors()
|
||||
{
|
||||
Vector3f[] vectors = new Vector3f[segments.length + 1];
|
||||
vectors[0] = segments[0].getStart();
|
||||
for ( int i = 0; i < segments.length; i++)
|
||||
vectors[i + 1] = segments[i].getEnd();
|
||||
|
||||
return vectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for retrieving the closest path segment to a reference point.
|
||||
*
|
||||
@@ -71,48 +84,40 @@ public class GesturePath {
|
||||
return closest(referencePoint).difference(referencePoint); // Get the closest segment and calculate the error.
|
||||
}
|
||||
|
||||
// Builder class for the GesturePath object.
|
||||
public static class Builder {
|
||||
|
||||
// List of vectors to add to the GesturePath object.
|
||||
private final List<Vector3f> vectors;
|
||||
/**
|
||||
* Function for converting a string to a GesturePath object.
|
||||
* The input string bytes will be directly converted into 3d vectors.
|
||||
* Every scalar is composed of 32 bits (4 characters), meaning 96 bits per vector.
|
||||
*
|
||||
* Note: ASCII to Vector conversion is done in Big Endian format (most significant byte first).
|
||||
*
|
||||
* @param input The string to convert
|
||||
* @return The GesturePath object
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructor for the Builder object.
|
||||
*
|
||||
* @param vectors The list of vectors to add.
|
||||
*/
|
||||
public Builder(List<Vector3f> vectors) {
|
||||
this.vectors = vectors;
|
||||
public static GesturePath fromString(String input) {
|
||||
byte[] bytes = input.getBytes();
|
||||
|
||||
// Check if the input string contains a valid amount of bytes (12 bytes per vector)
|
||||
if (input.length() % 12 != 0) {
|
||||
throw new IllegalArgumentException("Invalid input string length");
|
||||
}
|
||||
Vector3f[] vectors = new Vector3f[input.length() / 12];
|
||||
|
||||
/**
|
||||
* Default constructor for the Builder object.
|
||||
*/
|
||||
public Builder() {
|
||||
this.vectors = new ArrayList<>();
|
||||
float[] xyz = new float[3];
|
||||
for (int i = 0; i < bytes.length; i += 12) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
|
||||
xyz[j] = Float.intBitsToFloat(
|
||||
(bytes[i + j * 4] & 0xFF) << 24 |
|
||||
(bytes[i + j * 4 + 1] & 0xFF) << 16 |
|
||||
(bytes[i + j * 4 + 2] & 0xFF) << 8 |
|
||||
(bytes[i + j * 4 + 3] & 0xFF)
|
||||
);
|
||||
}
|
||||
vectors[i / 12] = new Vector3f(xyz[0], xyz[1], xyz[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a vector to the GesturePath object.
|
||||
*
|
||||
* @param vector The vector to add.
|
||||
* @return The Builder object.
|
||||
*/
|
||||
public Builder addVector(Vector3f vector) {
|
||||
vectors.add(vector);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the GesturePath object.
|
||||
*
|
||||
* @return The GesturePath object.
|
||||
*/
|
||||
public GesturePath build() {
|
||||
return new GesturePath(vectors.toArray(new Vector3f[0]));
|
||||
}
|
||||
|
||||
return new GesturePath(vectors);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,14 @@
|
||||
package com.example.fitbot.util.processing;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
|
||||
public interface IInputHandler {
|
||||
|
||||
/**
|
||||
* Function for accepting motion data and the transformed vector.
|
||||
* @param rotationVector The rotation vector of the motion data.
|
||||
* @param sensorId The sensor ID of the motion data.
|
||||
*/
|
||||
void accept(Vector3f rotationVector, int sensorId);
|
||||
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
package com.example.fitbot.util.processing;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
|
||||
public interface IMotionDataConsumer {
|
||||
|
||||
/**
|
||||
* Function for accepting motion data and the transformed vector.
|
||||
* @param transformedVector The transformed vector.
|
||||
* @param motionData The input motion data.
|
||||
* @param sampleIndex The index of the current sample
|
||||
* @param sampleRate The sample rate.
|
||||
*/
|
||||
void accept(Vector3f transformedVector, MotionData motionData, int sampleIndex, double sampleRate, int sensorId);
|
||||
|
||||
}
|
@@ -0,0 +1,236 @@
|
||||
package com.example.fitbot.util.processing;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.fitbot.exercise.Exercise;
|
||||
import com.example.fitbot.util.server.WebServer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
public class InputProcessor {
|
||||
|
||||
private Vector3f[][] selfRotationVectorPaths; // Relative path of the motion data
|
||||
private Vector3f[][] targetRotationVectorPaths; // Target path of the motion data
|
||||
|
||||
private final float sampleRate; // The sample rate of the motion sensor
|
||||
private float exerciseDuration;
|
||||
|
||||
// How many seconds have passed since the start of the exercise.
|
||||
// The exercise starts whenever the `startListening` function is called.
|
||||
private double secondsPassed = 0.0D;
|
||||
private long lastTime;
|
||||
|
||||
private IInputHandler motionDataConsumer = (rotationVector, deviceId) -> {
|
||||
};
|
||||
private WebServer server;
|
||||
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
selfRotationVectorPaths = new Vector3f[paths.length][(int) (exerciseTime * inputSampleRate)];
|
||||
targetRotationVectorPaths = paths;
|
||||
|
||||
this.sampleRate = inputSampleRate;
|
||||
this.exerciseDuration = exerciseTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for setting the exercise to use.
|
||||
* This updates the user and target path and the
|
||||
* duration of the exercise.
|
||||
* @param exercise The exercise to use the paths for.
|
||||
*/
|
||||
public void useExercise(Exercise exercise) {
|
||||
this.selfRotationVectorPaths = new Vector3f[2][(int) (exercise.exerciseTimeInSeconds * this.sampleRate)];
|
||||
this.targetRotationVectorPaths = new Vector3f[2][exercise.rightPath.getVectors().length];
|
||||
this.exerciseDuration = exercise.exerciseTimeInSeconds;
|
||||
this.secondsPassed = 0.0D;
|
||||
this.lastTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for checking if the exercise has finished.
|
||||
* @return True if the exercise has finished, false otherwise.
|
||||
*/
|
||||
public boolean hasFinished() {
|
||||
return this.secondsPassed >= this.exerciseDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for starting the listening process
|
||||
* of the motion sensor. This function will create
|
||||
* a new WebSocket server and start listening for
|
||||
* incoming connections.
|
||||
*/
|
||||
public void startListening() {
|
||||
// Create socket server
|
||||
this.server = WebServer.createServer();
|
||||
|
||||
Log.i("MotionProcessor", "Listening for incoming connections.");
|
||||
|
||||
// Check if the socket
|
||||
if (server != null) {
|
||||
// Update event handler to match our functionality.
|
||||
server.setEventHandler(this::parsePacket);
|
||||
this.secondsPassed = 0.0d;
|
||||
this.lastTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for stopping the listening process
|
||||
* of the motion sensor. This function will stop
|
||||
* the WebSocket server.
|
||||
*/
|
||||
public void stopListening() {
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
server = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for parsing arbitrary packet data.
|
||||
*
|
||||
* @param data The data to parse.
|
||||
*/
|
||||
public void parsePacket(@NotNull String data) {
|
||||
|
||||
try {
|
||||
|
||||
Log.i("MotionProcessor", "Received packet data: " + data);
|
||||
|
||||
JsonElement json = JsonParser.parseString(data);
|
||||
|
||||
if (!json.isJsonObject())
|
||||
return;
|
||||
|
||||
JsonObject object = json.getAsJsonObject();
|
||||
|
||||
String[] required = {
|
||||
"rotationX", "rotationY", "rotationZ",
|
||||
"type",
|
||||
"deviceId"
|
||||
};
|
||||
|
||||
// Ensure all properties are present in the received JSON object
|
||||
for (String s : required) {
|
||||
if (!object.has(s))
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the data
|
||||
Vector3f rotation = new Vector3f(object.get("rotationX").getAsFloat(), object.get("rotationY").getAsFloat(), object.get("rotationZ").getAsFloat());
|
||||
int deviceId = object.get("deviceId").getAsInt();
|
||||
String type = object.get("type").getAsString();
|
||||
|
||||
parseRotationVector(rotation, deviceId);
|
||||
} catch (Exception e) {
|
||||
Log.i("MotionProcessor", "Failed to parse packet data.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for adding motion data to the processor.
|
||||
*
|
||||
* @param rotation The rotation vector of the motion data.
|
||||
* @param deviceId The device ID of the motion data.
|
||||
*/
|
||||
public void parseRotationVector(Vector3f rotation, int deviceId) {
|
||||
if (deviceId >= 0 && deviceId < selfRotationVectorPaths.length) {
|
||||
|
||||
// Re-calculate time for index calculation
|
||||
secondsPassed = (System.currentTimeMillis() - lastTime) / 1000.0d;
|
||||
lastTime = System.currentTimeMillis();
|
||||
|
||||
// Supposed index of the current rotation vector in the `rotationVectorPaths` array
|
||||
int selfIndex = (int) (secondsPassed * sampleRate);
|
||||
|
||||
motionDataConsumer.accept(rotation, deviceId);
|
||||
if (selfIndex >= selfRotationVectorPaths[deviceId].length || selfIndex < 0)
|
||||
return;
|
||||
|
||||
selfRotationVectorPaths[deviceId][selfIndex] = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for getting the current progress of the exercise.
|
||||
* The return value will range between 0.0 and 1.0.
|
||||
*
|
||||
* @return The current progress of the exercise.
|
||||
*/
|
||||
public double getCurrentProgress()
|
||||
{
|
||||
return secondsPassed / exerciseDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 error offsets of the user's path compared to the
|
||||
* target path at a given point in time.
|
||||
*
|
||||
* @param sensorId The sensor ID to get the error offsets from.
|
||||
* @param time The time to get the error offsets from.
|
||||
* This value must be >= 0 && <= exerciseTime, otherwise
|
||||
* the error will be 0 by default.
|
||||
* @return A list of error offsets of the motion data compared to the reference path.
|
||||
*/
|
||||
public double getError(int sensorId, float time) {
|
||||
|
||||
// Ensure the sensor ID is within the bounds of the array
|
||||
if (sensorId < 0 || sensorId >= selfRotationVectorPaths.length)
|
||||
return 0.0d;
|
||||
|
||||
// Index of the current rotation vector
|
||||
int targetIndex = (int) ((this.exerciseDuration / this.targetRotationVectorPaths[sensorId].length) * time);
|
||||
int selfIndex = (int) (this.selfRotationVectorPaths[sensorId].length / this.sampleRate * time);
|
||||
|
||||
// Ensure the indexes are within the bounds of the array
|
||||
if (targetIndex >= 0 && targetIndex <= this.targetRotationVectorPaths[sensorId].length - 1 &&
|
||||
selfIndex >= 0 && selfIndex <= this.selfRotationVectorPaths[sensorId].length - 1)
|
||||
{
|
||||
return this.selfRotationVectorPaths[sensorId][selfIndex].distance(this.targetRotationVectorPaths[sensorId][targetIndex]);
|
||||
}
|
||||
return 0.0d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for getting the average error of the motion data
|
||||
* compared to the reference path.
|
||||
*
|
||||
* @param sensorId The sensor ID to get the error offsets from.
|
||||
* @return The average error of the motion data compared to the reference path.
|
||||
*/
|
||||
public double getAverageError(int sensorId) {
|
||||
double error = 0;
|
||||
for (int i = 0; i < this.exerciseDuration; i++) {
|
||||
error += getError(sensorId, i);
|
||||
}
|
||||
return error / this.exerciseDuration;
|
||||
}
|
||||
|
||||
public float secondsPassed() {
|
||||
return (float) secondsPassed;
|
||||
}
|
||||
}
|
@@ -1,68 +0,0 @@
|
||||
package com.example.fitbot.util.processing;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class MotionData {
|
||||
|
||||
// Data of the motion sensor
|
||||
public Vector3f acceleration, rotation;
|
||||
public int sensorId;
|
||||
|
||||
// Delimiter for the data received from the motion sensor
|
||||
private static final String DATA_DELIMITER = ";";
|
||||
|
||||
/**
|
||||
* Constructor for the MotionData class.
|
||||
*
|
||||
* @param accelerationX The acceleration in the X axis in m/s^2.
|
||||
* @param accelerationY The acceleration in the Y axis in m/s^2.
|
||||
* @param accelerationZ The acceleration in the Z axis in m/s^2.
|
||||
* @param rotationX The rotation in the X axis in degrees.
|
||||
* @param rotationY The rotation in the Y axis in degrees.
|
||||
* @param rotationZ The rotation in the Z axis in degrees.
|
||||
* @param sensorId The sensor id.
|
||||
*/
|
||||
public MotionData(float accelerationX, float accelerationY, float accelerationZ, float rotationX, float rotationY, float rotationZ, int sensorId) {
|
||||
this(new Vector3f(accelerationX, accelerationY, accelerationZ), new Vector3f(rotationX, rotationY, rotationZ), sensorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for the MotionData class.
|
||||
*
|
||||
* @param acceleration The acceleration vector in m/s^2.
|
||||
* @param rotation The rotation vector in degrees.
|
||||
*/
|
||||
public MotionData(Vector3f acceleration, Vector3f rotation, int sensorId) {
|
||||
this.acceleration = acceleration;
|
||||
this.rotation = rotation;
|
||||
this.sensorId = sensorId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for decoding a string into a MotionData object.
|
||||
* This string must contain the data of the motion sensor
|
||||
* separated by the delimiter. (;)
|
||||
*
|
||||
* @param data The string containing the data of the motion sensor.
|
||||
* @return An instance of MotionData.
|
||||
*/
|
||||
public static MotionData decode(String data) {
|
||||
Objects.requireNonNull(data); // Ensure data is not null
|
||||
|
||||
String[] parts = data.split(DATA_DELIMITER);
|
||||
if (parts.length != 7)
|
||||
return null;
|
||||
|
||||
return new MotionData(
|
||||
Float.parseFloat(parts[0]),
|
||||
Float.parseFloat(parts[1]),
|
||||
Float.parseFloat(parts[2]),
|
||||
Float.parseFloat(parts[3]),
|
||||
Float.parseFloat(parts[4]),
|
||||
Float.parseFloat(parts[5]),
|
||||
Integer.parseInt(parts[6])
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,233 +0,0 @@
|
||||
package com.example.fitbot.util.processing;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.fitbot.util.path.GesturePath;
|
||||
import com.example.fitbot.util.server.WebServer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.joml.Matrix3d;
|
||||
import org.joml.Vector3d;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MotionProcessor {
|
||||
|
||||
public static final String DELIMITER = ";";
|
||||
|
||||
private final List<Vector3f> relativeLeftPath = new ArrayList<>(); // Relative path of the left motion data
|
||||
private final List<Vector3f> relativeRightPath = new ArrayList<>(); // Relative path of the motion data
|
||||
|
||||
private Vector3f ZERO = new Vector3f(0, 0, 0);
|
||||
|
||||
private final float sampleRate = 1.0f / 10.0F; // samples/second
|
||||
|
||||
private IMotionDataConsumer motionDataConsumer = (p1, p2, p3, p4, p5) -> { };
|
||||
private WebServer server;
|
||||
|
||||
|
||||
public MotionProcessor() {}
|
||||
|
||||
|
||||
/**
|
||||
* Function for starting the listening process
|
||||
* of the motion sensor. This function will create
|
||||
* a new WebSocket server and start listening for
|
||||
* incoming connections.
|
||||
*/
|
||||
public void startListening() {
|
||||
// Create socket server
|
||||
this.server = WebServer.createServer();
|
||||
|
||||
Log.i("MotionProcessor", "Listening for incoming connections.");
|
||||
|
||||
// Check if the socket
|
||||
if (server != null) {
|
||||
// Update event handler to match our functionality.
|
||||
server.setEventHandler(this::parsePacket);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for stopping the listening process
|
||||
* of the motion sensor. This function will stop
|
||||
* the WebSocket server.
|
||||
*/
|
||||
public void stopListening() {
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for parsing arbitrary packet data.
|
||||
*
|
||||
* @param data The data to parse.
|
||||
*/
|
||||
public void parsePacket(@NotNull String data) {
|
||||
|
||||
try {
|
||||
|
||||
Log.i("MotionProcessor", "Received packet data: " + data);
|
||||
|
||||
JsonElement json = JsonParser.parseString(data);
|
||||
|
||||
if (!json.isJsonObject())
|
||||
return;
|
||||
|
||||
JsonObject object = json.getAsJsonObject();
|
||||
|
||||
String[] required = {
|
||||
"rotationX", "rotationY", "rotationZ",
|
||||
"accelerationX", "accelerationY", "accelerationZ",
|
||||
"type",
|
||||
"deviceId"
|
||||
};
|
||||
|
||||
// Ensure all properties are present in the received JSON object
|
||||
for (String s : required) {
|
||||
if (!object.has(s))
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the data
|
||||
Vector3f rotation = new Vector3f(object.get("rotationX").getAsFloat(), object.get("rotationY").getAsFloat(), object.get("rotationZ").getAsFloat());
|
||||
Vector3f acceleration = new Vector3f(object.get("accelerationX").getAsFloat(), object.get("accelerationY").getAsFloat(), object.get("accelerationZ").getAsFloat());
|
||||
int deviceId = object.get("deviceId").getAsInt();
|
||||
String type = object.get("type").getAsString();
|
||||
MotionData motionData = new MotionData(rotation, acceleration, deviceId);
|
||||
|
||||
if (type.equals("calibrate")) {
|
||||
ZERO = getRelativeVector(motionData);
|
||||
return;
|
||||
}
|
||||
|
||||
addMotionData(motionData);
|
||||
} catch (Exception e) {
|
||||
Log.i("MotionProcessor", "Failed to parse packet data.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for adding motion data to the processor.
|
||||
*
|
||||
* @param data The motion data to add.
|
||||
*/
|
||||
public void addMotionData(MotionData data) {
|
||||
List<Vector3f> target;
|
||||
if (data.sensorId == 0)
|
||||
target = relativeLeftPath;
|
||||
else target = relativeRightPath;
|
||||
Vector3f previous = target.isEmpty() ? ZERO : target.get(target.size() - 1);
|
||||
Vector3f relativeVector = getRelativeVector(data).add(previous);
|
||||
target.add(relativeVector);
|
||||
motionDataConsumer.accept(relativeVector, data, target.size(), this.sampleRate, data.sensorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for updating the relative path.
|
||||
*
|
||||
* @param relativeRightPath The new relative path.
|
||||
*/
|
||||
public void setRelativePaths(List<Vector3f> relativeLeftPath, List<Vector3f> relativeRightPath) {
|
||||
this.relativeRightPath.clear();
|
||||
this.relativeLeftPath.clear();
|
||||
this.relativeLeftPath.addAll(relativeLeftPath);
|
||||
this.relativeRightPath.addAll(relativeRightPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for setting the motion data receiver.
|
||||
*
|
||||
* @param consumer The consumer to set.
|
||||
*/
|
||||
public void setMotionDataEventHandler(IMotionDataConsumer consumer) {
|
||||
if (consumer != null)
|
||||
this.motionDataConsumer = consumer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for getting the relative vector of the motion data.
|
||||
* This function will calculate the relative position of the motion data
|
||||
* based on its acceleration and rotation vectors. This has to be done since
|
||||
* the acceleration vector is relative to its own rotation vector.
|
||||
*
|
||||
* @param motionData The motion data to calculate the relative vector for.
|
||||
* @return The relative vector of the motion data.
|
||||
*/
|
||||
public Vector3f getRelativeVector(MotionData motionData) {
|
||||
|
||||
// Rotate the acceleration vector back by the rotation vector to make it
|
||||
// perpendicular to the gravity vector, then apply double integration to get the relative position.
|
||||
// s = 1/2 * a * t^2
|
||||
// Step 2: Create rotation matrices for each axis
|
||||
// Step 4: Rotate the acceleration vector
|
||||
|
||||
return motionData.rotation
|
||||
.mul(5);
|
||||
/*return motionData.acceleration
|
||||
.rotateZ(-motionData.rotation.z)
|
||||
.rotateY(-motionData.rotation.y)
|
||||
.rotateX(-motionData.rotation.x)
|
||||
.mul(sampleRate * sampleRate / 2);*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for getting the error offsets of the provided path and the
|
||||
* received motion data.
|
||||
*
|
||||
* @param referencePath The reference path to compare the motion data to.
|
||||
* @return A list of error offsets of the motion data compared to the reference path.
|
||||
*/
|
||||
public List<Double> getErrors(GesturePath referencePath) {
|
||||
|
||||
List<Double> errors = new ArrayList<>();
|
||||
for (Vector3f vector : relativeRightPath) {
|
||||
errors.add(referencePath.getError(vector));
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for getting the error of the motion data compared to the reference path.
|
||||
*
|
||||
* @param path The path to compare the motion data to.
|
||||
* @param referencePoint The reference point to compare the motion data to.
|
||||
* @return The error of the motion data compared to the reference path.
|
||||
*/
|
||||
public double getError(GesturePath path, Vector3f referencePoint) {
|
||||
return path.getError(referencePoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for calculating the average error of the motion data
|
||||
* compared to the reference path.
|
||||
*
|
||||
* @param referencePath The reference path to compare the motion data to.
|
||||
* @return The average error of the motion data compared to the reference path.
|
||||
*/
|
||||
public double getAverageError(GesturePath referencePath, int sensorId) {
|
||||
double error = 0;
|
||||
for (Double e : getErrors(referencePath)) {
|
||||
error += e;
|
||||
}
|
||||
return error / Math.max(1, (sensorId == 0 ? relativeLeftPath : relativeRightPath).size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for logging the statistics of the motion data.
|
||||
*
|
||||
* @param referencePath The reference path to compare the motion data to.
|
||||
*/
|
||||
public void logStatistics(GesturePath referencePath) {
|
||||
Log.i("MotionProcessor", "Path length: " + relativeRightPath.size());
|
||||
Log.i("MotionProcessor", "Sample rate: " + sampleRate);
|
||||
Log.i("MotionProcessor", "Calibration point: " + ZERO.toString());
|
||||
}
|
||||
}
|
@@ -22,7 +22,7 @@ public class WebServer implements Runnable {
|
||||
protected IWebServerHandler eventHandler = (input) -> {}; // No-op.
|
||||
|
||||
private Thread thread;
|
||||
private AtomicBoolean forceClose = new AtomicBoolean(false);
|
||||
private final AtomicBoolean forceClose = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* Constructor for creating a new WebSocket server.
|
||||
|
@@ -2,11 +2,12 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<corners
|
||||
android:radius="20dp"
|
||||
android:radius="40dp"
|
||||
/>
|
||||
|
||||
<gradient
|
||||
android:startColor="#660000"
|
||||
android:endColor="#990000"
|
||||
android:startColor="#990000"
|
||||
android:endColor="#FF0000"
|
||||
android:angle="90"/>
|
||||
|
||||
</shape>
|
@@ -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/darkBlue" />
|
||||
<stroke android:width="4dp" android:color="@color/white" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
@@ -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="#FF0000" />
|
||||
<corners android:radius="10dp" />
|
||||
</shape>
|
@@ -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/lightBlue" />
|
||||
<stroke android:width="2dp" android:color="#FF0000" />
|
||||
<corners android:radius="20dp" />
|
||||
</shape>
|
@@ -0,0 +1,5 @@
|
||||
<vector android:height="48dp" android:tint="?attr/colorControlNormal"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||
</vector>
|
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/listview_background_shape">
|
||||
<stroke android:width="2dp" android:color="#ff207d94" />
|
||||
<padding android:left="2dp"
|
||||
android:top="2dp"
|
||||
android:right="2dp"
|
||||
android:bottom="2dp" />
|
||||
<corners android:radius="45dp" />
|
||||
<solid android:color="#F0F0F0" />
|
||||
</shape>
|
@@ -2,11 +2,12 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<corners
|
||||
android:radius="30dp"
|
||||
android:radius="25dp"
|
||||
/>
|
||||
|
||||
<gradient
|
||||
android:startColor="#990000"
|
||||
android:endColor="#FF0000"
|
||||
android:angle="90"/>
|
||||
|
||||
</shape>
|
@@ -1,77 +1,113 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="@color/red"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.activities.EndScreenActivity">
|
||||
android:background="@color/darkBlue"
|
||||
tools:context=".ui.activities.HelpActivity">
|
||||
|
||||
<View
|
||||
android:id="@+id/myRectangleView"
|
||||
android:layout_width="720dp"
|
||||
android:layout_height="270dp"
|
||||
android:layout_marginStart="320dp"
|
||||
android:layout_marginTop="25dp"
|
||||
android:layout_marginEnd="320dp"
|
||||
android:layout_marginBottom="25dp"
|
||||
android:background="@drawable/rectangle"
|
||||
app:layout_constraintBottom_toTopOf="@+id/homeButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
<LinearLayout
|
||||
android:layout_width="800dp"
|
||||
android:layout_height="450dp"
|
||||
android:layout_marginStart="80dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="@drawable/help2_background"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:background="@drawable/help_background"
|
||||
android:orientation="vertical"
|
||||
android:paddingVertical="15dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewTitleEndScreen"
|
||||
style="@style/TextStyleTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/afgerond" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="700dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/help_background"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewDescriptionEndScreen"
|
||||
style="@style/TextStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/voltooid"
|
||||
android:textAlignment="center"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="700dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/help_background"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewScoreEndScreen"
|
||||
style="@style/TextStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/score"
|
||||
android:textAlignment="center"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/homeButton"
|
||||
style="@style/ButtonStyle"
|
||||
android:layout_width="278dp"
|
||||
android:layout_height="117dp"
|
||||
android:layout_marginStart="501dp"
|
||||
android:layout_marginTop="25dp"
|
||||
android:layout_marginEnd="501dp"
|
||||
android:layout_marginBottom="50dp"
|
||||
android:text="Home"
|
||||
app:layout_constraintBottom_toTopOf="@+id/continueButton"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/myRectangleView" />
|
||||
android:id="@+id/homeButtonEndScreen"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="75dp"
|
||||
android:layout_marginEnd="280dp"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:background="@drawable/red_button_gradient"
|
||||
android:drawableTop="@drawable/ic_baseline_home_48"
|
||||
android:drawableTint="@color/white"
|
||||
android:padding="15dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/continueButton"
|
||||
style="@style/ButtonStyle"
|
||||
android:layout_width="280dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginStart="500dp"
|
||||
android:layout_marginTop="25dp"
|
||||
android:layout_marginEnd="500dp"
|
||||
android:layout_marginBottom="140dp"
|
||||
android:text="Continue"
|
||||
android:id="@+id/startButtonEndScreen"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="75dp"
|
||||
android:layout_marginStart="280dp"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:background="@drawable/red_button_gradient"
|
||||
android:padding="15dp"
|
||||
android:text="@string/start"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="30sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/homeButton" />
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gefeliciteerdText"
|
||||
style="@style/TextStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Gefeliciteerd"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.155" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/workoutText"
|
||||
style="@style/TextStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="u heeft de work out voltooid"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/myRectangleView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/myRectangleView" />
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
@@ -5,79 +5,53 @@ xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black"
|
||||
android:background="@color/darkBlue"
|
||||
android:fitsSystemWindows="true"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
tools:context=".ui.activities.FitnessActivity"
|
||||
tools:openDrawer="start">
|
||||
|
||||
<VideoView
|
||||
android:id="@+id/videoView"
|
||||
android:layout_width="450dp"
|
||||
android:layout_height="450dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
<LinearLayout
|
||||
android:layout_width="900dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:padding="15dp"
|
||||
android:background="@drawable/help2_background"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.04"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.2" />
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.example.fitbot.ui.components.PersonalMotionPreviewElement
|
||||
android:id="@+id/personalMotionPreviewElement"
|
||||
android:layout_width="450dp"
|
||||
android:layout_height="450dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.96"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.2" />
|
||||
<VideoView
|
||||
android:id="@+id/videoView"
|
||||
android:layout_width="400dp"
|
||||
android:layout_height="400dp"
|
||||
android:layout_marginStart="20dp"
|
||||
/>
|
||||
|
||||
<com.example.fitbot.ui.components.PersonalMotionPreviewElement
|
||||
android:id="@+id/personalMotionPreviewElement"
|
||||
android:layout_width="400dp"
|
||||
android:layout_height="400dp"
|
||||
android:layout_marginStart="20dp"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/homeButton"
|
||||
android:id="@+id/homeButtonFitness"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="75dp"
|
||||
android:layout_marginStart="404dp"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:background="@drawable/red_button_gradient"
|
||||
android:text="@string/home"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="30sp"
|
||||
android:drawableTop="@drawable/ic_baseline_home_48"
|
||||
android:drawableTint="@color/white"
|
||||
android:padding="15dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.02"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.968" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/skipButton"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="75dp"
|
||||
android:background="@drawable/red_button_gradient"
|
||||
android:text="@string/skip"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="30sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.968" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonComplete"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="75dp"
|
||||
android:background="@drawable/red_button_gradient"
|
||||
android:text="@string/complete"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="30sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.979"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.968" />
|
||||
tools:ignore="SpeakableTextPresentCheck" />
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
@@ -4,61 +4,98 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black"
|
||||
android:background="@color/darkBlue"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".ui.activities.HelpActivity">
|
||||
|
||||
<View
|
||||
android:id="@+id/myRectangleView"
|
||||
android:layout_width="1075dp"
|
||||
android:layout_height="510dp"
|
||||
android:background="@drawable/rectangle"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.502"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.168"
|
||||
tools:ignore="MissingConstraints" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/homeButton"
|
||||
android:layout_width="200dp"
|
||||
android:id="@+id/homeButtonHelp"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="75dp"
|
||||
android:layout_marginStart="406dp"
|
||||
android:layout_marginEnd="406dp"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:background="@drawable/red_button_gradient"
|
||||
android:text="@string/home"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="30sp"
|
||||
android:drawableTop="@drawable/ic_baseline_home_48"
|
||||
android:drawableTint="@color/white"
|
||||
android:padding="15dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.852" />
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView4.2"
|
||||
style="@style/TextStyle"
|
||||
android:layout_width="1053dp"
|
||||
android:layout_height="191dp"
|
||||
android:text="@string/uitleg"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.502"
|
||||
<LinearLayout
|
||||
android:layout_width="800dp"
|
||||
android:layout_height="450dp"
|
||||
android:layout_marginStart="80dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="@drawable/help2_background"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.133" />
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:background="@drawable/help_background"
|
||||
android:orientation="vertical"
|
||||
android:paddingVertical="15dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewTitleHelp"
|
||||
style="@style/TextStyleTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/help" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="700dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/help_background"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewStartHelp"
|
||||
style="@style/TextStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/uitlegStart"
|
||||
android:textAlignment="center"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="700dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/help_background"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewHomeHelp"
|
||||
style="@style/TextStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/uitlegHome"
|
||||
android:textAlignment="center"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView4"
|
||||
style="@style/TextStyle"
|
||||
android:layout_width="1053dp"
|
||||
android:layout_height="191dp"
|
||||
android:text="Als je klaar bent kunt u op de COMPLETE knop drukken in het sport scherm en dan kunt u uw punten inzien"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.502"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.482" />
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
@@ -4,9 +4,7 @@
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#232323"
|
||||
android:fitsSystemWindows="true"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
tools:context=".ui.activities.MainActivity"
|
||||
tools:openDrawer="start">
|
||||
|
||||
@@ -26,66 +24,95 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView2"
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="232dp"
|
||||
android:layout_marginStart="215dp"
|
||||
android:layout_marginTop="120dp"
|
||||
android:text="@string/welkom_bij_fitbot"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="64sp"
|
||||
android:layout_marginEnd="215dp"
|
||||
android:layout_marginBottom="23dp"
|
||||
android:background="@drawable/box_background"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/startButtonMain"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="240dp"
|
||||
android:layout_marginTop="200dp"
|
||||
android:text="@string/robot_helpt"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="32sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<TextView
|
||||
android:id="@+id/textViewWelkom"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/welkom_bij_fitbot"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="64sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewFit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/robot_helpt"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="32sp"
|
||||
android:translationX="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/startButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginStart="380dp"
|
||||
android:layout_marginTop="280dp"
|
||||
android:background="@drawable/red_button_gradient"
|
||||
android:id="@+id/startButtonMain"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="365dp"
|
||||
android:layout_marginTop="315dp"
|
||||
android:layout_marginEnd="365dp"
|
||||
android:layout_marginBottom="22dp"
|
||||
android:background="@drawable/big_red_button_gradient"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="25dp"
|
||||
android:paddingVertical="5dp"
|
||||
android:text="@string/start"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="80sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/helpButtonMain"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.727" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/helpButton"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="428dp"
|
||||
android:layout_marginTop="400dp"
|
||||
android:background="@drawable/darkred_button_gradient"
|
||||
android:id="@+id/helpButtonMain"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="420dp"
|
||||
android:layout_marginTop="444dp"
|
||||
android:layout_marginEnd="420dp"
|
||||
android:background="@drawable/red_button_gradient"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingVertical="5dp"
|
||||
android:text="@string/help"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="40sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView2"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_marginTop="340dp"
|
||||
android:contentDescription="@string/robot_logo"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.258" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageViewRobotLogo"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:contentDescription="@string/robot_logo"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:srcCompat="@drawable/robot_logo_inverted" />
|
||||
|
||||
|
||||
@@ -96,7 +123,11 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:background="@color/darkBlue"
|
||||
app:headerLayout="@layout/header"
|
||||
app:itemIconTint="@color/white"
|
||||
app:itemTextColor="@color/white"
|
||||
|
||||
app:menu="@menu/main_menu" />
|
||||
|
||||
</android.support.v4.widget.DrawerLayout>
|
@@ -24,7 +24,7 @@
|
||||
android:layout_marginTop="60dp"
|
||||
android:text="FitBot"
|
||||
android:textSize="48sp"
|
||||
android:textColor="@color/black"
|
||||
android:textColor="@color/darkBlue"
|
||||
app:layout_constraintStart_toEndOf="@+id/imageView"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
@@ -3,6 +3,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#00000000"
|
||||
android:elevation="8dp">
|
||||
|
||||
android:elevation="8dp"
|
||||
android:theme="@style/ToolbarNav">
|
||||
</android.support.v7.widget.Toolbar>
|
@@ -12,20 +12,14 @@
|
||||
|
||||
<group android:checkableBehavior="single">
|
||||
|
||||
<item android:title="Options">
|
||||
|
||||
<menu>
|
||||
<item
|
||||
android:id="@+id/nav_settings"
|
||||
android:icon="@drawable/ic_baseline_settings_48"
|
||||
android:title="Settings" />
|
||||
<item
|
||||
android:id="@+id/nav_rate"
|
||||
android:icon="@drawable/ic_baseline_star_rate_48"
|
||||
android:title="Rate" />
|
||||
</menu>
|
||||
|
||||
</item>
|
||||
<item
|
||||
android:id="@+id/nav_settings"
|
||||
android:icon="@drawable/ic_baseline_settings_48"
|
||||
android:title="Settings" />
|
||||
<item
|
||||
android:id="@+id/nav_rate"
|
||||
android:icon="@drawable/ic_baseline_star_rate_48"
|
||||
android:title="Rate" />
|
||||
|
||||
</group>
|
||||
|
||||
|
@@ -7,4 +7,11 @@
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
<color name="darkBlue">#1C1C27</color>
|
||||
<color name="midBlue">#24242F</color>
|
||||
<color name="lightBlue">#2C2C37</color>
|
||||
<color name="invertedBackground">#FFFFFF</color>
|
||||
<color name="invertedTextColor">#000000</color>
|
||||
<color name="invertedIconTint">#000000</color>
|
||||
</resources>
|
||||
|
||||
|
@@ -4,8 +4,10 @@
|
||||
|
||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
||||
<string name="navigation_drawer_close">Open navigation close</string>
|
||||
|
||||
<string name="welkom_bij_fitbot">Welkom bij FitBot</string>
|
||||
<string name="robot_helpt">de robot die helpt om fit te blijven</string>
|
||||
|
||||
<string name="start">Start</string>
|
||||
<string name="help">Help</string>
|
||||
<string name="todo">TODO</string>
|
||||
@@ -14,7 +16,11 @@
|
||||
<string name="skip">Skip</string>
|
||||
<string name="complete">Complete</string>
|
||||
|
||||
<string name="uitleg">Als je op de startknop drukt komen oefingen op het scherm. Het doel is om die zo goedmogelijk na te doen zodat je punten verzameld. Als je klaar bent kunt u op de COMPLETE knop drukken in het sport scherm en dan kunt u uw punten inzien</string>
|
||||
<color name="red">#f22b1d</color>
|
||||
<string name="uitlegStart">Druk op Start om de oefening te beginnen</string>
|
||||
<string name="uitlegHome">Ga terug naar het begin scherm door op het huisje te klikken</string>
|
||||
|
||||
<string name="afgerond">Oefeningen afgerond</string>
|
||||
<string name="voltooid">U heeft de oefeningen voltooid! \n Druk op start om nog een sessie te beginnen</string>
|
||||
<string name="score">Score:</string>
|
||||
|
||||
</resources>
|
@@ -2,8 +2,11 @@
|
||||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:colorBackground">@color/darkBlue</item>
|
||||
</style>
|
||||
|
||||
<style name="ToolbarNav" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:textColorSecondary">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="ButtonStyle">
|
||||
@@ -16,10 +19,16 @@
|
||||
</style>
|
||||
|
||||
<style name="TextStyle">
|
||||
<item name="android:textSize">36sp</item>
|
||||
<item name="android:textColor">#000000</item>
|
||||
<item name="android:textSize">25sp</item>
|
||||
<item name="android:textColor">#FFFFFF</item>
|
||||
<item name= "android:textStyle">bold</item>
|
||||
<item name="android:padding">6dp</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="TextStyleTitle">
|
||||
<item name="android:textSize">50sp</item>
|
||||
<item name="android:textColor">#D3D3D3</item>
|
||||
<item name= "android:textStyle">bold</item>
|
||||
<item name="android:padding">6dp</item>
|
||||
</style>
|
||||
</resources>
|
@@ -0,0 +1,29 @@
|
||||
package com.example.fitbot;
|
||||
|
||||
import com.example.fitbot.exercise.EMuscleGroup;
|
||||
import com.example.fitbot.exercise.Exercise;
|
||||
import com.example.fitbot.exercise.ExerciseManager;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class DatabaseFetchingTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void testDatabaseFetching() {
|
||||
Exercise exercise = ExerciseManager.retrieveExercise();
|
||||
assert exercise != null;
|
||||
System.out.println("\n---------------------------------");
|
||||
System.out.println("Exercise:");
|
||||
System.out.println("Name: " + exercise.title);
|
||||
System.out.println("Description: " + exercise.description);
|
||||
System.out.println("Muscle Group: " + exercise.muscleGroup);
|
||||
System.out.println("Image URL: " + exercise.imageUrl);
|
||||
System.out.println("Video URL: " + exercise.videoUrl);
|
||||
System.out.println("Exercise Time: " + exercise.exerciseTimeInSeconds);
|
||||
System.out.println("\n---------------------------------");
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
Reference in New Issue
Block a user