2024-05-30 14:16:17 +02:00
39 changed files with 1161 additions and 965 deletions

View File

@@ -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_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_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_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/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/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/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-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/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_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_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_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/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/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_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_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_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_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/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/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.125" /> <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_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_fitness.xml" value="0.23550724637681159" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.1" /> <entry key="app/src/main/res/layout/activity_main.xml" value="0.1" />

View File

@@ -38,9 +38,6 @@ dependencies {
implementation 'org.joml:joml:1.10.5' implementation 'org.joml:joml:1.10.5'
implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.google.code.gson:gson:2.8.6'
testImplementation 'junit:junit:4.13.2' 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:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.aldebaran:qisdk:1.7.5' implementation 'com.aldebaran:qisdk:1.7.5'

View File

@@ -3,21 +3,31 @@ package com.example.fitbot.exercise;
public enum EMuscleGroup { public enum EMuscleGroup {
// TODO: Implement // TODO: Implement
TORSO(0), TORSO(0, new String[]{"upper body", "torso"}),
ARMS(1), ARMS(1, new String[]{"arms", "arm", "shoulder"}),
LEGS(2), LEGS(2, new String[]{"Lower body", "legs", "leg"});
BALANCE(3);
int muscleGroupIdentifier; int muscleGroupIdentifier;
String[] muscleGroupNames;
EMuscleGroup(int identifier) { EMuscleGroup(int identifier, String[] muscleGroupNames) {
this.muscleGroupIdentifier = identifier; this.muscleGroupIdentifier = identifier;
this.muscleGroupNames = muscleGroupNames;
} }
public int getIdentifier() { public int getIdentifier() {
return this.muscleGroupIdentifier; 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) { public static EMuscleGroup parse(int identifier) {
for (EMuscleGroup muscleGroup : EMuscleGroup.values()) { for (EMuscleGroup muscleGroup : EMuscleGroup.values()) {
if (muscleGroup.getIdentifier() == identifier) { if (muscleGroup.getIdentifier() == identifier) {

View File

@@ -3,26 +3,21 @@ package com.example.fitbot.exercise;
import android.util.Log; import android.util.Log;
import com.example.fitbot.util.path.GesturePath; 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.IWebServerHandler;
import com.example.fitbot.util.server.WebServer; import com.example.fitbot.util.server.WebServer;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer;
public class Exercise implements IWebServerHandler { public class Exercise {
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 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. * Constructor for the AbstractExercise class.
@@ -32,106 +27,17 @@ public class Exercise implements IWebServerHandler {
* @param rightPath The path of the right hand. * @param rightPath The path of the right hand.
* @param title The title of the exercise. * @param title The title of the exercise.
* @param description The description of the exercise. * @param description The description of the exercise.
* @param segmentsPerSecond The number of segments per second. * @param imageUrl The URL of the image.
* This determines how fast the exercise should be performed. * @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.muscleGroup = muscleGroup;
this.title = title; this.title = title;
this.description = description; this.description = description;
this.leftPath = leftPath; this.leftPath = leftPath;
this.rightPath = rightPath; this.rightPath = rightPath;
} this.imageUrl = imageUrl;
this.videoUrl = videoUrl;
/** this.exerciseTimeInSeconds = exerciseTimeInSeconds;
* 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) {
} }
} }

View File

@@ -4,25 +4,35 @@ import com.example.fitbot.util.path.GesturePath;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import org.joml.Vector3f;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
public class ExerciseManager { 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_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_NAME = "name";
private static final String PROPERTY_MUSCLE_GROUP = "muscle_group"; private static final String PROPERTY_DATA = "data";
private static final String PROPERTY_SEGMENT_SPEED = "segment_speed"; 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; private static final float DEFAULT_SEGMENT_SPEED = 1.0f;
@@ -36,13 +46,17 @@ public class ExerciseManager {
* *
* @return The response from the server. * @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 { try {
URLConnection connection = new URL(url).openConnection(); URLConnection connection = new URL(url).openConnection();
connection.setDoOutput(true);
connection.setDoInput(true);
connection.addRequestProperty("Content-Type", contentType); connection.addRequestProperty("Content-Type", contentType);
connection.addRequestProperty("Request-Method", method); connection.addRequestProperty("Request-Method", method);
connection.getOutputStream().write(body.getBytes());
connection.connect(); connection.connect();
// Send a body if it is present
if (body != null)
connection.getOutputStream().write(body.getBytes());
InputStream stream = connection.getInputStream(); InputStream stream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
@@ -60,23 +74,41 @@ public class ExerciseManager {
/** /**
* Function for retrieving an exercise from the Raspberry Pi Database. * 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. * @return The exercise, if it exists on the server. Otherwise null.
*/ */
public static Exercise retrieveExercise(String uniqueIdentifier) { public static Exercise retrieveExercise() {
String response = sendHTTP( String response = sendHTTP(
HOST_ADDRESS + "/acquire", "GET", "application/json", "{\"kind\":\"" + uniqueIdentifier + "\"}" HOST_ADDRESS, "POST", "application/json", null
); );
// Validate the response // Validate the response
if (response != null) { if (response != null) {
try { try {
JsonObject content = JsonParser.parseString(response).getAsJsonObject(); 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( 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_NAME).getAsString(),
content.get(PROPERTY_DESC).getAsString(), content.get(PROPERTY_DESC).getAsString(),
gesturePathFromString(content.get(PROPERTY_VECTORS).getAsString()), content.get(PROPERTY_IMAGE_URL).getAsString(),
gesturePathFromString(content.get(PROPERTY_SEGMENT_SPEED).getAsString()) content.get(PROPERTY_VIDEO_URL).getAsString(),
GesturePath.fromString(leftRightData[0]),
GesturePath.fromString(leftRightData[1]),
DEFAULT_SEGMENT_SPEED
); );
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@@ -84,42 +116,4 @@ public class ExerciseManager {
} }
return null; 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();
}
} }

View File

@@ -14,7 +14,7 @@ public class EndScreenActivity extends AppCompatActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_end_screen); 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.homeButtonEndScreen, MainActivity.class);
com.example.fitbot.util.ButtonNavigation.setupButtonNavigation(this, R.id.continueButton, FitnessActivity.class); com.example.fitbot.util.ButtonNavigation.setupButtonNavigation(this, R.id.startButtonEndScreen, FitnessActivity.class);
} }
} }

View File

@@ -2,6 +2,7 @@ package com.example.fitbot.ui.activities;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.widget.VideoView; import android.widget.VideoView;
import com.aldebaran.qi.sdk.QiContext; 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.RobotActivity;
import com.aldebaran.qi.sdk.design.activity.conversationstatus.SpeechBarDisplayStrategy; import com.aldebaran.qi.sdk.design.activity.conversationstatus.SpeechBarDisplayStrategy;
import com.example.fitbot.R; import com.example.fitbot.R;
import com.example.fitbot.exercise.EMuscleGroup;
import com.example.fitbot.exercise.Exercise; import com.example.fitbot.exercise.Exercise;
import com.example.fitbot.exercise.ExerciseManager;
import com.example.fitbot.ui.components.PersonalMotionPreviewElement; import com.example.fitbot.ui.components.PersonalMotionPreviewElement;
import com.example.fitbot.util.ButtonNavigation; import com.example.fitbot.util.ButtonNavigation;
import com.example.fitbot.util.FitnessCycle; 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 org.joml.Vector3f;
import java.util.concurrent.CompletableFuture;
public class FitnessActivity extends RobotActivity implements RobotLifecycleCallbacks { 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 @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
QiSDK.register(this, this); QiSDK.register(this, this);
setContentView(R.layout.activity_fitness); setContentView(R.layout.activity_fitness);
// Remove the ugly ass bar on top of the view // 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 // Find the VideoView by its ID
VideoView videoView = findViewById(R.id.videoView); VideoView videoView = findViewById(R.id.videoView);
FitnessCycle.playVideo(videoView, this); FitnessCycle.playVideo(videoView, this);
ButtonNavigation.setupButtonNavigation(this, R.id.homeButtonFitness, MainActivity.class);
ButtonNavigation.setupButtonNavigation(this, R.id.homeButton, MainActivity.class);
ButtonNavigation.setupButtonNavigation(this, R.id.buttonComplete, EndScreenActivity.class);
// Implement your logic when the robot focus is gained // 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 @Override
public void onRobotFocusGained(QiContext qiContext) { public void onRobotFocusGained(QiContext qiContext) {
this.qiContext = qiContext;
// Find the VideoView by its ID // Find the VideoView by its ID
// CompletableFuture.runAsync(() -> FitnessCycle.executeMovement("bicepcurl", 10, qiContext)); // 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); personalMotionPreviewElement.provideQiContext(qiContext);
// FitnessCycle.playVideo(qiContext, videoView, this); // 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 @Override
public void onRobotFocusLost() { public void onRobotFocusLost() {
// Implement your logic when the robot focus is lost // Implement your logic when the robot focus is lost
@@ -94,6 +125,8 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
QiSDK.unregister(this, this); QiSDK.unregister(this, this);
this.personalMotionPreviewElement.onDestroy(); this.motionProcessor.stopListening();
this.motionProcessor = null;
this.personalMotionPreviewElement.destroy();
} }
} }

View File

@@ -2,6 +2,7 @@ package com.example.fitbot.ui.activities;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import com.example.fitbot.R; import com.example.fitbot.R;
import com.example.fitbot.util.ButtonNavigation; import com.example.fitbot.util.ButtonNavigation;
@@ -13,7 +14,18 @@ public class HelpActivity extends AppCompatActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_help); 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);
} }
} }

View File

@@ -11,6 +11,8 @@ import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button; import android.widget.Button;
import com.example.fitbot.R; import com.example.fitbot.R;
@@ -18,20 +20,24 @@ import com.example.fitbot.util.ButtonNavigation;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
//Variables // Variables
DrawerLayout drawerLayout; DrawerLayout drawerLayout;
NavigationView navigationView; NavigationView navigationView;
Toolbar toolbar; Toolbar toolbar;
Button startButton; Button startButton;
@SuppressLint("WrongViewCast") @SuppressLint("WrongViewCast")
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); 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 -> { startButton.setOnClickListener(v -> {
Uri videoUri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.bicepvideo); Uri videoUri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.bicepvideo);
Intent intent = new Intent(MainActivity.this, FitnessActivity.class); Intent intent = new Intent(MainActivity.this, FitnessActivity.class);
@@ -39,7 +45,11 @@ public class MainActivity extends AppCompatActivity {
startActivity(intent); startActivity(intent);
}); });
setUpUi(); // Set up the UI // Set up the UI
setUpUi();
// Hide system UI
hideSystemUI();
} }
private void setUpUi() { private void setUpUi() {
@@ -47,31 +57,65 @@ public class MainActivity extends AppCompatActivity {
drawerLayout = findViewById(R.id.drawer_layout); drawerLayout = findViewById(R.id.drawer_layout);
navigationView = findViewById(R.id.nav_view); navigationView = findViewById(R.id.nav_view);
toolbar = findViewById(R.id.toolbar); toolbar = findViewById(R.id.toolbar);
startButton = findViewById(R.id.startButton); startButton = findViewById(R.id.startButtonMain);
ButtonNavigation.setupButtonNavigation(this, R.id.startButton, FitnessActivity.class); // Hide the action bar
ButtonNavigation.setupButtonNavigation(this, R.id.helpButton, HelpActivity.class); if (getSupportActionBar() != null) {
getSupportActionBar().hide();
}
ButtonNavigation.setupButtonNavigation(this, R.id.startButtonMain, FitnessActivity.class);
ButtonNavigation.setupButtonNavigation(this, R.id.helpButtonMain, HelpActivity.class);
/*---Tool Bar---*/ /*---Tool Bar---*/
setSupportActionBar(toolbar); // Make the toolbar act as the action 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---*/ /*---Navigation Drawer Menu---*/
navigationView.bringToFront(); // Make the navigation drawer menu clickable navigationView.bringToFront(); // Make the navigation drawer menu clickable
ActionBarDrawerToggle toggle=new // Create a toggle for the navigation drawer 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(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); drawerLayout.addDrawerListener(toggle);
toggle.syncState(); // Synchronize the state of the navigation drawer 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 @Override
public void onBackPressed(){ // Close the navigation drawer when the back button is pressed public void onWindowFocusChanged(boolean hasFocus) {
if(drawerLayout.isDrawerOpen(GravityCompat.START)){ super.onWindowFocusChanged(hasFocus);
drawerLayout.closeDrawer(GravityCompat.START); 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();
} }
} }
} }

View File

@@ -1,18 +1,23 @@
package com.example.fitbot.ui.components; package com.example.fitbot.ui.components;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path; import android.support.annotation.Nullable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import com.aldebaran.qi.sdk.QiContext; import com.aldebaran.qi.sdk.QiContext;
import com.example.fitbot.exercise.Exercise; 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.FitnessCycle;
import com.example.fitbot.util.path.GesturePath; import com.example.fitbot.util.processing.IInputHandler;
import com.example.fitbot.util.processing.MotionProcessor; import com.example.fitbot.util.processing.InputProcessor;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Vector2f; import org.joml.Vector2f;
@@ -20,61 +25,58 @@ import org.joml.Vector3f;
import org.joml.Vector4f; import org.joml.Vector4f;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
public class PersonalMotionPreviewElement extends View { public class PersonalMotionPreviewElement extends View implements IInputHandler {
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;
// Fields regarding Exercise and speech handling.
private InputProcessor motionProcessor;
private Exercise exercise; private Exercise exercise;
private QiContext qiContext;
private int exerciseCount;
private Path targetPath; // The path the user is supposed to follow. private FitnessActivity parentActivity;
private Path actualPath; // The path the user is currently following.
private final Paint referencePaint = new Paint(); private final Paint userProgressPaint = new Paint();
private final Paint targetPaint = new Paint(); private final Paint borderPaint = new Paint();
private final Paint backgroundColor = 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 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 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 final Vector4f objectPosition = new Vector4f(0, 0, 0, 1); // The location of the object in 3D space.
private ConcurrentLinkedQueue<Vector2f> vectors = new ConcurrentLinkedQueue<>(); private ConcurrentLinkedQueue<Vector2f> vectors = new ConcurrentLinkedQueue<>();
// TODO: Remove
private Vector2f[] axisVectors = new Vector2f[0]; private Vector2f[] axisVectors = new Vector2f[0];
private static final String[] STARTING_PHRASES = {
private static final String[] USER_PHRASES = {
"Veel success met de oefening!", "Veel success met de oefening!",
"Je kan het!", "Je kan het!",
"Veel plezier!" "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) { public PersonalMotionPreviewElement(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
this.referencePaint.setColor(0xFFFF0000); // Red if ( context instanceof Activity)
this.referencePaint.setStyle(Paint.Style.FILL); {
this.referencePaint.setStrokeWidth(5.0f); this.parentActivity = (FitnessActivity) context;
this.referencePaint.setAntiAlias(true); }
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. // Target paint is the filling of the target path.
this.targetPaint.setColor(-1); this.borderPaint.setColor(-1);
this.targetPaint.setStyle(Paint.Style.STROKE); this.borderPaint.setStyle(Paint.Style.STROKE);
this.targetPaint.setStrokeWidth(5.0f); this.borderPaint.setStrokeWidth(5.0f);
this.targetPaint.setAntiAlias(true); 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). * will cause the vertex projections to fail (0 width and height).
* *
* @param exercise The exercise that the user is currently performing. * @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."); 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.exercise = exercise;
this.paths = exercise.getPath(); this.exerciseCount = exerciseCount;
// TODO: Remove
this.axisVectors = new Vector2f[] { this.axisVectors = new Vector2f[] {
projectVertex(new Vector3f(-5.0f, 0, 0), getWidth(), getHeight()),
projectVertex(new Vector3f(-100.0f, 0, 0), getWidth(), getHeight()), projectVertex(new Vector3f(5.0f, 0, 0), getWidth(), getHeight()),
projectVertex(new Vector3f(100.0f, 0, 0), getWidth(), getHeight()), projectVertex(new Vector3f(0, -5.0f, 0), getWidth(), getHeight()),
projectVertex(new Vector3f(0, -100.0f, 0), getWidth(), getHeight()), projectVertex(new Vector3f(0, 5.0f, 0), getWidth(), getHeight()),
projectVertex(new Vector3f(0, 100.0f, 0), getWidth(), getHeight()), projectVertex(new Vector3f(0, 0, -5.0f), getWidth(), getHeight()),
projectVertex(new Vector3f(0, 0, -100.0f), getWidth(), getHeight()), projectVertex(new Vector3f(0, 0, 5.0f), getWidth(), getHeight())
projectVertex(new Vector3f(0, 0, 100.0f), getWidth(), getHeight())
}; };
} }
public void onDestroy() public void destroy()
{ {
if ( this.motionProcessor != null ) if ( this.motionProcessor != null )
this.motionProcessor.stopListening(); this.motionProcessor.stopListening();
@@ -128,35 +124,55 @@ public class PersonalMotionPreviewElement extends View {
*/ */
public void provideQiContext(QiContext context) { public void provideQiContext(QiContext context) {
this.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. // Handler that is called every time the motion processor receives new data.
this.motionProcessor.setMotionDataEventHandler((processed, preprocessed, sampleIndex, sampleRate, deviceId) -> { this.motionProcessor.setInputHandler((rotationVector, deviceId) -> {
int progress = (int)this.motionProcessor.getError(this.paths[0], processed);
this.exerciseProgress.set(Math.min(1000, Math.max(0, progress))); Log.i("MotionProcessor", "Rotation vector received: " + rotationVector);
Log.i("MotionProcessor", "Processed data: " + progress + " (" + preprocessed + ")"); Log.i("MotionProcessor", "Last error offset:" + this.motionProcessor.getError(deviceId, this.motionProcessor.secondsPassed()));
Vector2f parsed = projectVertex(processed, this.getWidth(), this.getHeight());
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); this.vectors.add(parsed);
// Remove the first element if the array is too big // Remove the first element if the array is too big
if (this.vectors.size() > 100) if (this.vectors.size() > 100)
this.vectors.poll(); this.vectors.poll();
}); });
saySomethingNice();
}
/**
* Function to say something nice to the user :)
*/
private void saySomethingNice()
{
if (this.qiContext == null) if (this.qiContext == null)
return; 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. * @param exercise The exercise that the user is currently performing.
*/ */
public void setExercise(Exercise exercise) { public void setExercise(Exercise exercise) {
this.motionProcessor.useExercise(exercise);
this.exercise = exercise; this.exercise = exercise;
} }
@@ -178,12 +195,12 @@ public class PersonalMotionPreviewElement extends View {
// Perspective transformation conserves the depth of the object // Perspective transformation conserves the depth of the object
projectionMatrix projectionMatrix
.identity() .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 // Convert world coordinates to screen-space using MVP matrix
Vector4f screenCoordinates = new Vector4f(point, 1.0f) 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) // Normalize screen coordinates from (-1, 1) to (0, virtualWidth) and (0, virtualHeight)
float normalizedX = (screenCoordinates.x / screenCoordinates.w + 1.0f) * 0.5f * virtualWidth; float normalizedX = (screenCoordinates.x / screenCoordinates.w + 1.0f) * 0.5f * virtualWidth;
@@ -195,23 +212,23 @@ public class PersonalMotionPreviewElement extends View {
@Override @Override
public void onDraw(Canvas canvas) { public void onDraw(Canvas canvas) {
canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundColor); canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundPaint);
this.setBackgroundColor(0xFF000000); // Black this.setBackgroundColor(0xFF000000); // Black
/*if (this.exercise == null) /*if (this.exercise == null)
return;*/ return;*/
for (int i = 0, startX, endX, startY, endY; i < axisVectors.length/2; i++) 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)); startX = (int)Math.max(0, Math.min(this.getWidth(), (int)axisVectors[i*2].x));
endX = (int)Math.max(0, Math.min(this.screenDimensions.x, (int)axisVectors[i*2+1].x)); endX = (int)Math.max(0, Math.min(this.getWidth(), (int)axisVectors[i*2+1].x));
startY = (int)Math.max(0, Math.min(this.screenDimensions.y, (int)axisVectors[i*2].y)); startY = (int)Math.max(0, Math.min(this.getHeight(), (int)axisVectors[i*2].y));
endY = (int)Math.max(0, Math.min(this.screenDimensions.y, (int)axisVectors[i*2+1].y)); endY = (int)Math.max(0, Math.min(this.getHeight(), (int)axisVectors[i*2+1].y));
canvas.drawLine(startX, startY, endX, endY, this.targetPaint); canvas.drawLine(startX, startY, endX, endY, this.borderPaint);
} }
for ( Vector2f point : this.vectors) 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 // Draw target circle
@@ -228,7 +245,10 @@ public class PersonalMotionPreviewElement extends View {
);*/ );*/
this.invalidate(); this.invalidate();
}
@Override
public void accept(Vector3f rotationVector, int sensorId) {
timePassed = (System.nanoTime() - startingTime) / 1E9D;
} }
} }

View File

@@ -43,6 +43,19 @@ public class GesturePath {
return segments; 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. * 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. 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
*/
/** public static GesturePath fromString(String input) {
* Constructor for the Builder object. byte[] bytes = input.getBytes();
*
* @param vectors The list of vectors to add. // Check if the input string contains a valid amount of bytes (12 bytes per vector)
*/ if (input.length() % 12 != 0) {
public Builder(List<Vector3f> vectors) { throw new IllegalArgumentException("Invalid input string length");
this.vectors = vectors;
} }
Vector3f[] vectors = new Vector3f[input.length() / 12];
/** float[] xyz = new float[3];
* Default constructor for the Builder object. for (int i = 0; i < bytes.length; i += 12) {
*/ for (int j = 0; j < 3; j++) {
public Builder() {
this.vectors = new ArrayList<>(); 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]);
} }
return new GesturePath(vectors);
/**
* 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]));
}
} }
} }

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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])
);
}
}

View File

@@ -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());
}
}

View File

@@ -22,7 +22,7 @@ public class WebServer implements Runnable {
protected IWebServerHandler eventHandler = (input) -> {}; // No-op. protected IWebServerHandler eventHandler = (input) -> {}; // No-op.
private Thread thread; private Thread thread;
private AtomicBoolean forceClose = new AtomicBoolean(false); private final AtomicBoolean forceClose = new AtomicBoolean(false);
/** /**
* Constructor for creating a new WebSocket server. * Constructor for creating a new WebSocket server.

View File

@@ -2,11 +2,12 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners <corners
android:radius="20dp" android:radius="40dp"
/> />
<gradient <gradient
android:startColor="#660000" android:startColor="#990000"
android:endColor="#990000" android:endColor="#FF0000"
android:angle="90"/> android:angle="90"/>
</shape> </shape>

View File

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

View File

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

View File

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

View File

@@ -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>

View File

@@ -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>

View File

@@ -2,11 +2,12 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners <corners
android:radius="30dp" android:radius="25dp"
/> />
<gradient <gradient
android:startColor="#990000" android:startColor="#990000"
android:endColor="#FF0000" android:endColor="#FF0000"
android:angle="90"/> android:angle="90"/>
</shape> </shape>

View File

@@ -1,77 +1,113 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/red"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.activities.EndScreenActivity"> android:background="@color/darkBlue"
tools:context=".ui.activities.HelpActivity">
<View <LinearLayout
android:id="@+id/myRectangleView" android:layout_width="800dp"
android:layout_width="720dp" android:layout_height="450dp"
android:layout_height="270dp" android:layout_marginStart="80dp"
android:layout_marginStart="320dp" android:layout_marginTop="24dp"
android:layout_marginTop="25dp" android:background="@drawable/help2_background"
android:layout_marginEnd="320dp" android:orientation="vertical"
android:layout_marginBottom="25dp" android:padding="16dp"
android:background="@drawable/rectangle"
app:layout_constraintBottom_toTopOf="@+id/homeButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" 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 <Button
android:id="@+id/homeButton" android:id="@+id/homeButtonEndScreen"
style="@style/ButtonStyle" android:layout_width="150dp"
android:layout_width="278dp" android:layout_height="75dp"
android:layout_height="117dp" android:layout_marginEnd="280dp"
android:layout_marginStart="501dp" android:layout_marginBottom="30dp"
android:layout_marginTop="25dp" android:background="@drawable/red_button_gradient"
android:layout_marginEnd="501dp" android:drawableTop="@drawable/ic_baseline_home_48"
android:layout_marginBottom="50dp" android:drawableTint="@color/white"
android:text="Home" android:padding="15dp"
app:layout_constraintBottom_toTopOf="@+id/continueButton" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/myRectangleView" />
<Button <Button
android:id="@+id/continueButton" android:id="@+id/startButtonEndScreen"
style="@style/ButtonStyle" android:layout_width="150dp"
android:layout_width="280dp" android:layout_height="75dp"
android:layout_height="120dp" android:layout_marginStart="280dp"
android:layout_marginStart="500dp" android:layout_marginBottom="30dp"
android:layout_marginTop="25dp" android:background="@drawable/red_button_gradient"
android:layout_marginEnd="500dp" android:padding="15dp"
android:layout_marginBottom="140dp" android:text="@string/start"
android:text="Continue" android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/homeButton" />
<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> </android.support.constraint.ConstraintLayout>

View File

@@ -5,79 +5,53 @@ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout" android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/black" android:background="@color/darkBlue"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
tools:context=".ui.activities.FitnessActivity" tools:context=".ui.activities.FitnessActivity"
tools:openDrawer="start"> tools:openDrawer="start">
<VideoView <LinearLayout
android:id="@+id/videoView" android:layout_width="900dp"
android:layout_width="450dp" android:layout_height="wrap_content"
android:layout_height="450dp" android:layout_gravity="center"
app:layout_constraintBottom_toBottomOf="parent" 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_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.04"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent">
app:layout_constraintVertical_bias="0.2" />
<com.example.fitbot.ui.components.PersonalMotionPreviewElement <VideoView
android:id="@+id/personalMotionPreviewElement" android:id="@+id/videoView"
android:layout_width="450dp" android:layout_width="400dp"
android:layout_height="450dp" android:layout_height="400dp"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="20dp"
app:layout_constraintEnd_toEndOf="parent" />
app:layout_constraintHorizontal_bias="0.96"
app:layout_constraintStart_toStartOf="parent" <com.example.fitbot.ui.components.PersonalMotionPreviewElement
app:layout_constraintTop_toTopOf="parent" android:id="@+id/personalMotionPreviewElement"
app:layout_constraintVertical_bias="0.2" /> android:layout_width="400dp"
android:layout_height="400dp"
android:layout_marginStart="20dp"
/>
</LinearLayout>
<Button <Button
android:id="@+id/homeButton" android:id="@+id/homeButtonFitness"
android:layout_width="150dp" android:layout_width="150dp"
android:layout_height="75dp" android:layout_height="75dp"
android:layout_marginStart="404dp"
android:layout_marginBottom="30dp"
android:background="@drawable/red_button_gradient" android:background="@drawable/red_button_gradient"
android:text="@string/home" android:drawableTop="@drawable/ic_baseline_home_48"
android:textAllCaps="false" android:drawableTint="@color/white"
android:textColor="@color/white" android:padding="15dp"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.02"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" tools:ignore="SpeakableTextPresentCheck" />
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" />
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>

View File

@@ -4,61 +4,98 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/black" android:background="@color/darkBlue"
android:fitsSystemWindows="true"
tools:context=".ui.activities.HelpActivity"> 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 <Button
android:id="@+id/homeButton" android:id="@+id/homeButtonHelp"
android:layout_width="200dp" android:layout_width="150dp"
android:layout_height="75dp" android:layout_height="75dp"
android:layout_marginStart="406dp"
android:layout_marginEnd="406dp"
android:layout_marginBottom="30dp"
android:background="@drawable/red_button_gradient" android:background="@drawable/red_button_gradient"
android:text="@string/home" android:drawableTop="@drawable/ic_baseline_home_48"
android:textAllCaps="false" android:drawableTint="@color/white"
android:textColor="@color/white" android:padding="15dp"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.852" />
<TextView <LinearLayout
android:id="@+id/textView4.2" android:layout_width="800dp"
style="@style/TextStyle" android:layout_height="450dp"
android:layout_width="1053dp" android:layout_marginStart="80dp"
android:layout_height="191dp" android:layout_marginTop="24dp"
android:text="@string/uitleg" android:background="@drawable/help2_background"
app:layout_constraintBottom_toBottomOf="parent" android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent" android:padding="16dp"
app:layout_constraintHorizontal_bias="0.502"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent">
app:layout_constraintVertical_bias="0.133" />
<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> </android.support.constraint.ConstraintLayout>

View File

@@ -4,9 +4,7 @@
android:id="@+id/drawer_layout" android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#232323"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
tools:context=".ui.activities.MainActivity" tools:context=".ui.activities.MainActivity"
tools:openDrawer="start"> tools:openDrawer="start">
@@ -26,66 +24,95 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView <LinearLayout
android:id="@+id/textView2"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="232dp" android:layout_marginStart="215dp"
android:layout_marginTop="120dp" android:layout_marginTop="120dp"
android:text="@string/welkom_bij_fitbot" android:layout_marginEnd="215dp"
android:textColor="@color/white" android:layout_marginBottom="23dp"
android:textSize="64sp" 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_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent">
<TextView <TextView
android:id="@+id/textView3" android:id="@+id/textViewWelkom"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="240dp" android:text="@string/welkom_bij_fitbot"
android:layout_marginTop="200dp" android:textColor="@color/white"
android:text="@string/robot_helpt" android:textSize="64sp" />
android:textColor="@color/white"
android:textSize="32sp" <TextView
app:layout_constraintStart_toStartOf="parent" android:id="@+id/textViewFit"
app:layout_constraintTop_toTopOf="parent" /> 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 <Button
android:id="@+id/startButton" android:id="@+id/startButtonMain"
android:layout_width="200dp" android:layout_width="wrap_content"
android:layout_height="100dp" android:layout_height="wrap_content"
android:layout_marginStart="380dp" android:layout_marginStart="365dp"
android:layout_marginTop="280dp" android:layout_marginTop="315dp"
android:background="@drawable/red_button_gradient" 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:text="@string/start"
android:textAlignment="center"
android:textAllCaps="false" android:textAllCaps="false"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="80sp" 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_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.727" />
<Button <Button
android:id="@+id/helpButton" android:id="@+id/helpButtonMain"
android:layout_width="100dp" android:layout_width="wrap_content"
android:layout_height="50dp" android:layout_height="wrap_content"
android:layout_marginStart="428dp" android:layout_marginStart="420dp"
android:layout_marginTop="400dp" android:layout_marginTop="444dp"
android:background="@drawable/darkred_button_gradient" android:layout_marginEnd="420dp"
android:background="@drawable/red_button_gradient"
android:gravity="center"
android:paddingHorizontal="20dp"
android:paddingVertical="5dp"
android:text="@string/help" android:text="@string/help"
android:textAlignment="center"
android:textAllCaps="false" android:textAllCaps="false"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="40sp" android:textSize="40sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
<ImageView
android:id="@+id/imageView2"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginTop="340dp"
android:contentDescription="@string/robot_logo"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="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" /> app:srcCompat="@drawable/robot_logo_inverted" />
@@ -96,7 +123,11 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start" android:layout_gravity="start"
android:background="@color/darkBlue"
app:headerLayout="@layout/header" app:headerLayout="@layout/header"
app:itemIconTint="@color/white"
app:itemTextColor="@color/white"
app:menu="@menu/main_menu" /> app:menu="@menu/main_menu" />
</android.support.v4.widget.DrawerLayout> </android.support.v4.widget.DrawerLayout>

View File

@@ -24,7 +24,7 @@
android:layout_marginTop="60dp" android:layout_marginTop="60dp"
android:text="FitBot" android:text="FitBot"
android:textSize="48sp" android:textSize="48sp"
android:textColor="@color/black" android:textColor="@color/darkBlue"
app:layout_constraintStart_toEndOf="@+id/imageView" app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />

View File

@@ -3,6 +3,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="#00000000" android:background="#00000000"
android:elevation="8dp"> android:elevation="8dp"
android:theme="@style/ToolbarNav">
</android.support.v7.widget.Toolbar> </android.support.v7.widget.Toolbar>

View File

@@ -12,20 +12,14 @@
<group android:checkableBehavior="single"> <group android:checkableBehavior="single">
<item android:title="Options"> <item
android:id="@+id/nav_settings"
<menu> android:icon="@drawable/ic_baseline_settings_48"
<item android:title="Settings" />
android:id="@+id/nav_settings" <item
android:icon="@drawable/ic_baseline_settings_48" android:id="@+id/nav_rate"
android:title="Settings" /> android:icon="@drawable/ic_baseline_star_rate_48"
<item android:title="Rate" />
android:id="@+id/nav_rate"
android:icon="@drawable/ic_baseline_star_rate_48"
android:title="Rate" />
</menu>
</item>
</group> </group>

View File

@@ -7,4 +7,11 @@
<color name="teal_700">#FF018786</color> <color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color> <color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
<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> </resources>

View File

@@ -4,8 +4,10 @@
<string name="navigation_drawer_open">Open navigation drawer</string> <string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Open navigation close</string> <string name="navigation_drawer_close">Open navigation close</string>
<string name="welkom_bij_fitbot">Welkom bij FitBot</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="robot_helpt">de robot die helpt om fit te blijven</string>
<string name="start">Start</string> <string name="start">Start</string>
<string name="help">Help</string> <string name="help">Help</string>
<string name="todo">TODO</string> <string name="todo">TODO</string>
@@ -14,7 +16,11 @@
<string name="skip">Skip</string> <string name="skip">Skip</string>
<string name="complete">Complete</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> <string name="uitlegStart">Druk op Start om de oefening te beginnen</string>
<color name="red">#f22b1d</color> <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> </resources>

View File

@@ -2,8 +2,11 @@
<resources> <resources>
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="windowActionBar">false</item> <item name="android:colorBackground">@color/darkBlue</item>
<item name="windowNoTitle">true</item> </style>
<style name="ToolbarNav" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:textColorSecondary">@color/white</item>
</style> </style>
<style name="ButtonStyle"> <style name="ButtonStyle">
@@ -16,10 +19,16 @@
</style> </style>
<style name="TextStyle"> <style name="TextStyle">
<item name="android:textSize">36sp</item> <item name="android:textSize">25sp</item>
<item name="android:textColor">#000000</item> <item name="android:textColor">#FFFFFF</item>
<item name= "android:textStyle">bold</item> <item name= "android:textStyle">bold</item>
<item name="android:padding">6dp</item>
</style> </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> </resources>

View File

@@ -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---------------------------------");
}
}

View File

@@ -0,0 +1,62 @@
# Infrastructure UML
``` mermaid
classDiagram
Raspberry pi --> NodeJS
Raspberry pi --> Database
NodeJS --> Androidapp : getExerciseData (Wifi, Rest API)
Database <--> NodeJS : Database queries
ESP8266 --> Androidapp : getRotationalData (Wifi)
namespace Server {
class Raspberry pi {
+MariaDB
+Apache2
+NodeJS
Database()
Webserver()
}
class Database {
+ExerciseID
+ExerciseName
+ExerciseDescription
+ExerciseVideo
+GyroCoordinates
+MuscleGroup
}
class NodeJS {
+MariaDB
GetRandomExercise()
}
}
namespace Pepper {
class Androidapp {
+Java
+Android SDK
+QiSDK
motionProcessing()
robotMovement()
showVideo()
fitnessCycle()
}
}
namespace Hardware {
class ESP8266{
+RotationalX
+RotationalY
+RotationalZ
Gyroscope()
}
}
```

View File

@@ -2,25 +2,34 @@
## Introduction ## Introduction
For this project we want to design an embedded system that can track a users position. We want to track their current position on the ground and see how they are shifting their weight. This system will be used to track their position to determine if a user is doing the exercises correctly. For this project we want to design an embedded system that can track a users position. We want to track their current position on the ground. This system will be used to track their position to determine if a user is doing the exercises correctly.
## Objectives ## Objectives
* Design an embedded system that can track user position. - Design an embedded system that can track user position.
* Develop an algorithm to process the data from the Wii Fit Board and determine the user's position. - Develop an algorithm to process the data from the sensor and determine the user's position.
- Sync the code to the current task for the user.
## Research and Analysis ## Research and Analysis
### Choosing the Wii Fit Board ### Choosing the sensor
For this project we have chosen the Wii Fit Board as our primary sensor. The Wii Fit Board is a balance board that can measure a user's weight and center of balance. It is a low-cost sensor that is easy to interface with a microcontroller. The Wii Fit Board communicates over Bluetooth, which makes it easy to connect to a microcontroller with Bluetooth capabilities. For this project we have chosen LDR's as our primary sensor. The LDR's will be placed on the ground in a board and the user will stand on top of the board. The LDR's will be used to track the user's position. The LDR's will be connected to the esp32s3 microcontroller and the data will be processed to determine the user's position.
We have chosen this sensor since it's one of the easiest and cheapest solutions to our problem. Other sensors like pressure sensors, accelerometers, and Wii Balance Board are either too expensive, not the most optimal for the task, or hard to integrate with other systems.
### Alternative Solutions ### Alternative Solutions
There are other sensors that can be used for position tracking, such as pressure sensors or accelerometers. However, these sensors are more expensive and may require additional processing to determine the user's position. The Wii Fit Board provides a simple and cost-effective solution for position tracking. There are other sensors that can be used for position tracking, such as pressure sensors, Wii Balance Board or accelerometers. However, these sensors are either too expensive, not the most optimal for the task or hard to integrate with other systems.
Example of other sensors that can be used for position tracking: Example of other sensors that can be used for position tracking:
Wii Balance Board:
- Description: The Wii Balance Board is a balance board that can measure a user's weight and center of balance.
- Pros: Low-cost.
- Cons: Very hard to intergrate with other systems.
- Cost: ~ 20 euros (https://www.amazon.nl/Nintendo-Wii-Balance-Board-Wii/dp/B0013E9HP6)
Pressure sensors: Pressure sensors:
- Description: Pressure sensors can be used to measure the force applied by the user on the ground. By measuring the pressure distribution, the user's position can be determined. - Description: Pressure sensors can be used to measure the force applied by the user on the ground. By measuring the pressure distribution, the user's position can be determined.
- Pros: High accuracy, can measure force applied by the user. - Pros: High accuracy, can measure force applied by the user.
@@ -42,36 +51,31 @@ To be added
### Hardware ### Hardware
The hardware of the system will consist of the following components: The hardware of the system will consist of the following components:
- Wii Fit Board: The primary sensor for position tracking. - LDR: The sensor that will be used to track the user's position based on the light intensity.
- Pepper: The controller that will process the data from the Wii Fit Board. - ESP32S3: The microcontroller that will process the data from the LDR.
- Pepper: The controller that will recieve the processed data from the ESP32S3 and will sync the data to the current task for the user.
#### Connection diagram
To be added
### Software ### Software
The software of the system will consist of the following: To be added
- Wiiboard-simple: A library that will be used to transfer data from the Wii Fit Board to pepper.
- Position Tracking Algorithm: An algorithm that will process the sensor data and determine the user's position.
### Integration ### Integration
The Wii Fit Board will be connected to Pepper using the Wiiboard-simple library. The library will be used to read the sensor data from the Wii Fit Board and transfer it to Pepper. The position tracking algorithm will process the sensor data and determine the user's position. To be added
Challenge:
- Connecting to the wii fit board. It is not possible to connect directly to the Wii Fit Board, it is necessary to use a library that can interpret the data sent by the Wii Fit Board.
- The Wii Fit Balance Board sends data in a specific format. To interpret this data, it's necessary to understand the format and how to convert it to a usable format.
- The Wii Fit Balance Board uses Bluetooth 2.0 to communicate. Pepper uses Bluetooth 4.0 this means that there might be compatibility issues/latancy issues.
## Implementation ## Implementation
### Prototyping ### Prototyping
To start the implementation of the system, we will create a prototype that will read the sensor data from the Wii Fit Board and send it to your computer. Once we have the data, we will develop the position tracking algorithm to determine the user's position. After that, the algorithm will be integrated with pepper. To be added
### Testing and Validation ### Testing and Validation
Tests: To be added
- Test the prototype to ensure that it can read the sensor data from the Wii Fit Board.
- Test the position tracking algorithm to ensure that it can determine the user's position accurately.
- Test the integrated system to ensure that it can track the user's position in real-time.
## Conclusion ## Conclusion
@@ -79,9 +83,8 @@ To be added
## References ## References
[Wiiboard lib](https://code.google.com/archive/p/wiiboard-simple/wikis/Documentation.wiki) [Bluetooth Discovery](https://developer.android.com/develop/connectivity/bluetooth/find-bluetooth-devices)
[BlueSoil](https://advanti-lab.sb.dfki.de/?page_id=64)
[FitScales](https://github.com/paulburton/fitscales) ## Appendices
[WiiRemoteJ](https://github.com/micromu/WiiRemoteJ)
[Wiibrew Wiimote](https://wiibrew.org/wiki/Wiimote) To be added
[Wiibrew Balance Board](https://wiibrew.org/wiki/Wii_Balance_Board)

View File

@@ -294,13 +294,13 @@ To do
Done Done
- - skill ontwikkeling plan
**23 May** **23 May**
To do To do
- - NodeJs opzetten
Done Done
@@ -310,37 +310,30 @@ Done
To do To do
- - Pi fixen
- Sprint review
- Retrospective
Done Done
- - Pi fixen
- Sprint review
- Retrospective
**25 May** **25 May**
To do - Weekend
-
Done
-
**26 May** **26 May**
To do - Weekend / Betoog
-
Done
-
**27 May** **27 May**
To do To do
- - Betoog
- NodeJs opzetten
Done Done
@@ -350,27 +343,37 @@ Done
To do To do
- - Betoog
- NodeJs opzetten
Done Done
- - Betoog
- NodeJs opzetten
**29 May** **29 May**
To do To do
- - Connectie MariaDB fixen
- Ui Style aanpassen en overal het zelfde maken
- Ui in het Nederlands
Done Done
- - Connectie MariaDB fixen
- Ui Styke aanpassen en overal het zelfde maken
- Ui in het Nederlands
**30 May** **30 May**
To do To do
- - Ui end screen updaten
- Data ophalen uit database in Android App
- Data verwerken in App (oefeningen)
- Expert voorbereiden
- Skill ontwikkeling plan afmaken
Done Done

View File

@@ -0,0 +1,28 @@
# Expert 3 Sprint 3
---
## K1: Je hebt object georiënteerde software gemaakt die samenwerkt met een database.
Voor het bewijs van algemene kennis over K1 zie [Expert review 2 K1](../expertReview/expert2sprint2.md#K1:-Je-hebt-object-georiënteerde-software-gemaakt-die-samenwerkt-met-een-database.).
Deze sprint ben ik bezig geweest met:
- Functionaliteit van de database
- Functionaliteit van de server
- Data ophalen uit de database en weergeven in de app
---
## K3: Je hebt een infrastructuur ontworpen en gebouwd volgens zelf-gedefinieerde vereisten.
Feedback verwerkt (diagrammen)
Infrastructuur beschreven met problemen en oplossingen [Infrastuctuur](https://muupooviixee66-propedeuse-hbo-ict-onderwijs-2023-178fb5f296aa35.dev.hihva.nl/documentation/database/infrastructure/)
---
## K4: Je ontwerpt een embedded systeem op basis van gegeven hardware. & K5: Je kan software schrijven voor een intelligente controller voorzien van actuatoren en sensoren.
Research naar hardware en software voor de controller