Show workouts

This commit is contained in:
jannis 2019-08-17 20:39:57 +02:00
parent 4aa8a5c6aa
commit 7e406db592
28 changed files with 911 additions and 82 deletions

View File

@ -63,4 +63,5 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
}

View File

@ -32,9 +32,10 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".RecordWorkoutActivity"></activity>
<activity android:name=".ListWorkoutsActivity" />
<activity android:name=".LauncherActivity">
<activity android:name=".activity.ShowWorkoutActivity"></activity>
<activity android:name=".activity.RecordWorkoutActivity" />
<activity android:name=".activity.ListWorkoutsActivity" />
<activity android:name=".activity.LauncherActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -21,7 +21,10 @@ package de.tadris.fitness;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.room.Room;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import de.tadris.fitness.data.AppDatabase;
import de.tadris.fitness.location.LocationListener;
@ -45,6 +48,12 @@ public class Instance {
private Instance(Context context) {
db = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
.allowMainThreadQueries()
.addMigrations(new Migration(2, 3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE workout add topSpeed DOUBLE not null default 0.0");
}
})
.build();
locationListener= new LocationListener(context);
}

View File

@ -65,7 +65,7 @@ public class WorkoutAdapter extends RecyclerView.Adapter<WorkoutAdapter.WorkoutV
@Override
public void onBindViewHolder(WorkoutViewHolder holder, final int position) {
holder.lengthText.setText(UnitUtils.getDistance(workouts[position].length));
holder.timeText.setText(UnitUtils.getHourMinuteTime(workouts[position].getTime()));
holder.timeText.setText(UnitUtils.getHourMinuteTime(workouts[position].getDuration()));
holder.root.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -80,7 +80,7 @@ public class WorkoutAdapter extends RecyclerView.Adapter<WorkoutAdapter.WorkoutV
return workouts.length;
}
interface WorkoutAdapterListener{
public interface WorkoutAdapterListener{
void onItemClick(Workout workout);
}

View File

@ -17,12 +17,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.tadris.fitness;
package de.tadris.fitness.activity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import de.tadris.fitness.R;
public class LauncherActivity extends Activity {
@Override

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.tadris.fitness;
package de.tadris.fitness.activity;
import android.app.Activity;
import android.content.Intent;
@ -28,6 +28,9 @@ import android.view.MenuItem;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import de.tadris.fitness.Instance;
import de.tadris.fitness.R;
import de.tadris.fitness.WorkoutAdapter;
import de.tadris.fitness.data.Workout;
public class ListWorkoutsActivity extends Activity implements WorkoutAdapter.WorkoutAdapterListener {
@ -62,7 +65,8 @@ public class ListWorkoutsActivity extends Activity implements WorkoutAdapter.Wor
@Override
public void onItemClick(Workout workout) {
// TODO: open detail View
ShowWorkoutActivity.selectedWorkout= workout;
startActivity(new Intent(this, ShowWorkoutActivity.class));
}
@Override

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.tadris.fitness;
package de.tadris.fitness.activity;
import android.Manifest;
import android.app.Activity;
@ -30,23 +30,20 @@ import android.view.ViewGroup;
import androidx.core.app.ActivityCompat;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.util.AndroidUtil;
import org.mapsforge.map.android.view.MapView;
import org.mapsforge.map.layer.cache.TileCache;
import org.mapsforge.map.layer.download.TileDownloadLayer;
import de.tadris.fitness.Instance;
import de.tadris.fitness.R;
import de.tadris.fitness.data.Workout;
import de.tadris.fitness.location.LocationListener;
import de.tadris.fitness.location.MyLocationOverlay;
import de.tadris.fitness.location.WorkoutRecorder;
import de.tadris.fitness.map.HumanitarianTileSource;
import de.tadris.fitness.map.MapManager;
public class RecordWorkoutActivity extends Activity implements LocationListener.LocationChangeListener {
MapView mapView;
MyLocationOverlay locationOverlay;
TileDownloadLayer downloadLayer;
WorkoutRecorder recorder;
@ -57,24 +54,7 @@ public class RecordWorkoutActivity extends Activity implements LocationListener.
this.mapView= new MapView(this);
mapView.setZoomLevelMin((byte) 18);
mapView.setZoomLevelMax((byte) 18);
mapView.setBuiltInZoomControls(false);
TileCache tileCache = AndroidUtil.createTileCache(this, "mapcache", mapView.getModel().displayModel.getTileSize(), 1f, this.mapView.getModel().frameBufferModel.getOverdrawFactor(), true);
HumanitarianTileSource tileSource = HumanitarianTileSource.INSTANCE;
tileSource.setUserAgent("mapsforge-android");
downloadLayer = new TileDownloadLayer(tileCache, mapView.getModel().mapViewPosition, tileSource, AndroidGraphicFactory.INSTANCE);
mapView.getLayerManager().getLayers().add(downloadLayer);
locationOverlay= new MyLocationOverlay(Instance.getInstance(this).locationListener, getDrawable(R.drawable.location_marker));
mapView.getLayerManager().redrawLayers();
mapView.setZoomLevel((byte) 18);
mapView.setCenter(new LatLong(52, 13));
downloadLayer= MapManager.setupMap(mapView);
((ViewGroup)findViewById(R.id.recordMapViewrRoot)).addView(mapView);
@ -113,7 +93,6 @@ public class RecordWorkoutActivity extends Activity implements LocationListener.
@Override
public void onLocationChange(Location location) {
mapView.getModel().mapViewPosition.animateTo(LocationListener.locationToLatLong(location));
locationOverlay.setPosition(location.getLatitude(), location.getLongitude(), location.getAccuracy());
}
@Override

View File

@ -0,0 +1,228 @@
/*
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
*
* This file is part of FitoTrack
*
* FitoTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FitoTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.tadris.fitness.activity;
import android.app.Activity;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Handler;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.MapPosition;
import org.mapsforge.core.util.LatLongUtils;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.view.MapView;
import org.mapsforge.map.layer.download.TileDownloadLayer;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import de.tadris.fitness.Instance;
import de.tadris.fitness.R;
import de.tadris.fitness.data.Workout;
import de.tadris.fitness.data.WorkoutSample;
import de.tadris.fitness.map.MapManager;
import de.tadris.fitness.map.WorkoutLayer;
import de.tadris.fitness.util.ThemeManager;
import de.tadris.fitness.util.UnitUtils;
public class ShowWorkoutActivity extends Activity {
static Workout selectedWorkout;
List<WorkoutSample> samples;
Workout workout;
ViewGroup root;
Resources.Theme theme;
MapView map;
TileDownloadLayer downloadLayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
workout= selectedWorkout;
samples= Arrays.asList(Instance.getInstance(this).db.workoutDao().getAllSamplesOfWorkout(workout.id));
setTheme(ThemeManager.getThemeByWorkout(workout, this));
setContentView(R.layout.activity_show_workout);
getActionBar().setDisplayHomeAsUpEnabled(true);
theme= getTheme();
root= findViewById(R.id.showWorkoutRoot);
addTitle("Zeit");
addKeyValue("Datum", getDate(), "Dauer", UnitUtils.getHourMinuteTime(workout.getDuration()));
addKeyValue("Startzeit", SimpleDateFormat.getTimeInstance().format(new Date(workout.start)),
"Endzeit", SimpleDateFormat.getTimeInstance().format(new Date(workout.end)));
addKeyValue("Distanz", UnitUtils.getDistance(workout.length), "Pace", UnitUtils.round(workout.avgPace, 1) + " min/km");
addTitle("Geschwindigkeit");
addKeyValue("Durchschnittsgeschw.", UnitUtils.getSpeed(workout.avgSpeed),
"Top Geschw.", UnitUtils.round(workout.topSpeed, 1) + " km/h");
// TODO: add speed diagram
addTitle("Verbrauchte Energie");
addKeyValue("Gesamtverbrauch", workout.calorie + " kcal",
"Relativverbrauch", UnitUtils.round(((double)workout.calorie / workout.length / 1000), 2) + " kcal/km");
addTitle("Route");
addMap();
}
String getDate(){
return SimpleDateFormat.getDateInstance().format(new Date(workout.start));
}
void addTitle(String title){
TextView textView= new TextView(this);
textView.setText(title);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
textView.setTextColor(getThemePrimaryColor());
textView.setTypeface(Typeface.DEFAULT_BOLD);
textView.setAllCaps(true);
root.addView(textView);
}
void addKeyValue(String key1, String value1){
addKeyValue(key1, value1, "", "");
}
void addKeyValue(String key1, String value1, String key2, String value2){
View v= getLayoutInflater().inflate(R.layout.show_entry, root, false);
TextView title1= v.findViewById(R.id.v1title);
TextView title2= v.findViewById(R.id.v2title);
TextView v1= v.findViewById(R.id.v1value);
TextView v2= v.findViewById(R.id.v2value);
title1.setText(key1);
title2.setText(key2);
v1.setText(value1);
v2.setText(value2);
root.addView(v);
}
void addDiagram(){
}
void addMap(){
map= new MapView(this);
downloadLayer= MapManager.setupMap(map);
map.setZoomLevelMin((byte)2);
map.setZoomLevelMax((byte)18);
WorkoutLayer workoutLayer= new WorkoutLayer(samples, getThemePrimaryColor());
map.addLayer(workoutLayer);
final BoundingBox bounds= new BoundingBox(workoutLayer.getLatLongs()).extendMeters(50);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
map.getModel().mapViewPosition.setMapPosition(new MapPosition(bounds.getCenterPoint(),
(byte)(LatLongUtils.zoomForBounds(map.getDimension(), bounds, map.getModel().displayModel.getTileSize()))));
map.animate().alpha(1f).setDuration(1000).start();
}
}, 1000);
map.getModel().mapViewPosition.setMapLimit(bounds);
root.addView(map, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getWindowManager().getDefaultDisplay().getWidth()*3/4));
map.setAlpha(0);
}
private int getThemePrimaryColor() {
final TypedValue value = new TypedValue ();
getTheme().resolveAttribute (android.R.attr.colorPrimary, value, true);
return value.data;
}
@Override
protected void onDestroy() {
map.destroyAll();
AndroidGraphicFactory.clearResourceMemoryCache();
super.onDestroy();
}
@Override
public void onPause(){
super.onPause();
downloadLayer.onPause();
}
public void onResume(){
super.onResume();
downloadLayer.onResume();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.show_workout_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == R.id.actionDeleteWorkout){
// TODO: delete workout
return true;
}else if(id == android.R.id.home){
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
/*private int zoomToBounds(BoundingBox boundingBox) {
int TILE_SIZE= map.getModel().displayModel.getTileSize();
Dimension mapViewDimension = map.getModel().mapViewDimension.getDimension();
if(mapViewDimension == null)
return 0;
double dxMax = longitudeToPixelX(boundingBox.maxLongitude, (byte) 0) / TILE_SIZE;
double dxMin = longitudeToPixelX(boundingBox.minLongitude, (byte) 0) / TILE_SIZE;
double zoomX = floor(-log(3.8) * log(abs(dxMax-dxMin)) + mapViewDimension.width / TILE_SIZE);
double dyMax = latitudeToPixelY(boundingBox.maxLatitude, (byte) 0) / TILE_SIZE;
double dyMin = latitudeToPixelY(boundingBox.minLatitude, (byte) 0) / TILE_SIZE;
double zoomY = floor(-log(3.8) * log(abs(dyMax-dyMin)) + mapViewDimension.height / TILE_SIZE);
return Double.valueOf(min(zoomX, zoomY)).intValue();
}*/
}

View File

@ -22,7 +22,7 @@ package de.tadris.fitness.data;
import androidx.room.Database;
import androidx.room.RoomDatabase;
@Database(version = 1, entities = {Workout.class, WorkoutSample.class})
@Database(version = 3, entities = {Workout.class, WorkoutSample.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract WorkoutDao workoutDao();
}

View File

@ -26,6 +26,8 @@ import androidx.room.PrimaryKey;
public class Workout{
public static final String WORKOUT_TYPE_RUNNING= "running";
public static final String WORKOUT_TYPE_WALKING= "running";
public static final String WORKOUT_TYPE_HIKING= "hiking";
public static final String WORKOUT_TYPE_CYCLING= "cycling";
@PrimaryKey
@ -44,6 +46,11 @@ public class Workout{
*/
public double avgSpeed;
/**
* Top speed in m/s
*/
public double topSpeed;
/**
* Average pace of workout in km/min
*/
@ -51,11 +58,9 @@ public class Workout{
public String workoutType;
/**
* Gets time of workout
* @return time in milliseconds
*/
public long getTime(){
public int calorie;
public long getDuration(){
return end - start;
}

View File

@ -19,13 +19,19 @@
package de.tadris.fitness.data;
import android.content.Context;
import java.util.List;
import de.tadris.fitness.Instance;
import de.tadris.fitness.util.CalorieCalculator;
public class WorkoutManager {
public static void insertWorkout(Instance instance, Workout workout, List<WorkoutSample> samples){
public static void insertWorkout(Context context, Workout workout, List<WorkoutSample> samples){
AppDatabase db= Instance.getInstance(context).db;
workout.id= System.currentTimeMillis();
// Calculating values
@ -34,19 +40,28 @@ public class WorkoutManager {
length+= samples.get(i - 1).toLatLong().sphericalDistance(samples.get(i).toLatLong());
}
workout.length= (int)length;
workout.avgSpeed= ((double) workout.length / 1000) / ((double) workout.getTime() / 1000);
workout.avgPace= (double)(workout.getTime() / 1000 / 60) / ((double) workout.length / 1000);
workout.avgSpeed= ((double) workout.length) / ((double) workout.getDuration() / 1000);
workout.avgPace= (double)(workout.getDuration() / 1000 / 60) / ((double) workout.length / 1000);
workout.calorie= CalorieCalculator.calculateCalories(workout, 80);
// TODO: use user weight
// Setting workoutId in the samples
int i= 0;
double topSpeed= 0;
for(WorkoutSample sample : samples){
i++;
sample.id= workout.id + i;
sample.workoutId= workout.id;
if(sample.speed > topSpeed){
topSpeed= sample.speed;
}
}
workout.topSpeed= topSpeed;
// Saving workout and samples
instance.db.workoutDao().insertWorkoutAndSamples(workout, samples.toArray(new WorkoutSample[0]));
db.workoutDao().insertWorkoutAndSamples(workout, samples.toArray(new WorkoutSample[0]));
}

View File

@ -88,7 +88,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
if(state != RecordingState.STOPPED){
throw new IllegalStateException("Cannot save recording, recorder was not stopped. state = " + state);
}
WorkoutManager.insertWorkout(Instance.getInstance(context), workout, samples);
WorkoutManager.insertWorkout(context, workout, samples);
}
public int getSampleCount(){

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
*
* This file is part of FitoTrack
*
* FitoTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FitoTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.tadris.fitness.map;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.util.AndroidUtil;
import org.mapsforge.map.android.view.MapView;
import org.mapsforge.map.layer.cache.TileCache;
import org.mapsforge.map.layer.download.TileDownloadLayer;
public class MapManager {
public static TileDownloadLayer setupMap(MapView mapView){
mapView.setZoomLevelMin((byte) 18);
mapView.setZoomLevelMax((byte) 18);
mapView.setBuiltInZoomControls(false);
TileCache tileCache = AndroidUtil.createTileCache(mapView.getContext(), "mapcache",
mapView.getModel().displayModel.getTileSize(), 1f,
mapView.getModel().frameBufferModel.getOverdrawFactor(), true);
HumanitarianTileSource tileSource = HumanitarianTileSource.INSTANCE;
tileSource.setUserAgent("mapsforge-android");
TileDownloadLayer downloadLayer = new TileDownloadLayer(tileCache, mapView.getModel().mapViewPosition, tileSource, AndroidGraphicFactory.INSTANCE);
mapView.getLayerManager().getLayers().add(downloadLayer);
mapView.getLayerManager().redrawLayers();
mapView.setZoomLevel((byte) 18);
mapView.setCenter(new LatLong(52, 13));
return downloadLayer;
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
*
* This file is part of FitoTrack
*
* FitoTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FitoTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.tadris.fitness.map;
import org.mapsforge.core.graphics.Paint;
import org.mapsforge.core.graphics.Style;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.layer.overlay.Polyline;
import java.util.List;
import de.tadris.fitness.data.WorkoutSample;
public class WorkoutLayer extends Polyline {
public static Paint getDEFAULT_PAINT_STROKE(int color){
Paint paint= AndroidGraphicFactory.INSTANCE.createPaint();
paint.setStyle(Style.STROKE);
paint.setColor(color);
paint.setStrokeWidth(14f);
return paint;
}
List<WorkoutSample> samples;
public WorkoutLayer(List<WorkoutSample> samples, int color) {
this(getDEFAULT_PAINT_STROKE(color), samples);
}
public WorkoutLayer(Paint paintStroke, List<WorkoutSample> samples) {
super(paintStroke, AndroidGraphicFactory.INSTANCE);
this.samples = samples;
init();
}
private void init(){
for(WorkoutSample sample : samples){
addPoint(sample.toLatLong());
}
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
*
* This file is part of FitoTrack
*
* FitoTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FitoTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.tadris.fitness.util;
import de.tadris.fitness.data.Workout;
public class CalorieCalculator {
/**
*
* @param workout the workout
* @param weight the weight of the person in kilogram
* @return calories burned
*/
public static int calculateCalories(Workout workout, double weight){
int mins= (int)(workout.getDuration() / 1000 / 60);
return (int)(mins * (getMET(workout) * 3.5 * weight) / 200);
}
/**
* calorie calculation based on @link { https://www.topendsports.com/weight-loss/energy-met.htm }
*
* @param workout
* @return MET
*/
public static double getMET(Workout workout){
if(workout.workoutType.equals(Workout.WORKOUT_TYPE_RUNNING)){
if(workout.avgSpeed < 3.2){
return 1.5;
}else if(workout.avgSpeed < 4.0){
return 2.5;
}else if(workout.avgSpeed < 4.8){
return 3.25;
}else if(workout.avgSpeed < 5.9){
return 4.0;
}else if(workout.avgSpeed < 7.0){
return 5.0;
}else if(workout.avgSpeed < 8.0){
return 7.0;
}else if(workout.avgSpeed < 9.6){
return 9.0;
}else if(workout.avgSpeed < 12.8){
return 12.0;
}else{
return 16;
}
}
if(workout.workoutType.equals(Workout.WORKOUT_TYPE_HIKING)){
return 6.3;
}
if(workout.workoutType.equals(Workout.WORKOUT_TYPE_CYCLING)){
return Math.max(3, (workout.avgSpeed-10) / 1.5);
}
return -1;
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
*
* This file is part of FitoTrack
*
* FitoTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FitoTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.tadris.fitness.util;
import android.content.Context;
import de.tadris.fitness.R;
import de.tadris.fitness.data.Workout;
public class ThemeManager {
public static int getThemeByWorkout(Workout workout, Context context){
switch (workout.workoutType){
case Workout.WORKOUT_TYPE_RUNNING: return R.style.Running;
case Workout.WORKOUT_TYPE_CYCLING: return R.style.Bicycling;
default: return R.style.AppTheme;
}
}
}

View File

@ -28,9 +28,9 @@ public class UnitUtils {
int remainingMinutes= (int)mins % 60;
if(hours > 0){
return hours + "h " + remainingMinutes + "m";
return hours + " h " + remainingMinutes + " m";
}else{
return remainingMinutes + "min";
return remainingMinutes + " min";
}
}
@ -40,6 +40,7 @@ public class UnitUtils {
* @return String in preferred unit
*/
public static String getDistance(int distance){
// TODO: use preferred unit by user
if(distance >= 1000){
return getDistanceInKilometers((double)distance);
}else{
@ -47,16 +48,26 @@ public class UnitUtils {
}
}
/**
*
* @param speed speed in m/s
* @return speed in km/h
*/
public static String getSpeed(double speed){
// TODO: use preferred unit by user
return round(speed*3.6, 1) + " km/h";
}
public static String getDistanceInMeters(int distance){
return distance + "m";
return distance + " m";
}
public static String getDistanceInKilometers(double distance){
return round(distance / 1000, 1) + "km";
return round(distance / 1000, 1) + " km";
}
public static double round(double d, int count){
return (double)Math.round(d * Math.pow(10, count)) / count;
return (double)Math.round(d * Math.pow(10, count)) / Math.pow(10, count);
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
*
* This file is part of FitoTrack
*
* FitoTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FitoTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.tadris.fitness.util;
import de.tadris.fitness.data.Workout;
public class WorkoutTypeCalculator {
public static String getType(Workout workout){
// TODO: use localisation
if(workout.workoutType.equals(Workout.WORKOUT_TYPE_RUNNING)){
if(workout.avgSpeed < 7){
return "Walking";
}else if(workout.avgSpeed < 9.6){
return "Jogging";
}else{
return "Running";
}
}
if(workout.workoutType.equals(Workout.WORKOUT_TYPE_CYCLING)){
return "Cycling";
}
if(workout.workoutType.equals(Workout.WORKOUT_TYPE_HIKING)){
return "Hiking";
}
return "Unknown";
}
}

View File

@ -1,10 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
~
~ This file is part of FitoTrack
~
~ FitoTrack is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ FitoTrack is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ListWorkoutsActivity">
tools:context=".activity.ListWorkoutsActivity">
<androidx.recyclerview.widget.RecyclerView

View File

@ -1,7 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
~
~ This file is part of FitoTrack
~
~ FitoTrack is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ FitoTrack is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LauncherActivity"/>
tools:context=".activity.LauncherActivity" />

View File

@ -22,7 +22,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RecordWorkoutActivity">
tools:context=".activity.RecordWorkoutActivity">
<FrameLayout
android:layout_width="match_parent"
@ -30,6 +30,13 @@
android:layout_above="@id/recordInfoRoot">
<ImageView
android:id="@+id/imageView"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:src="@drawable/location_marker" />
<LinearLayout
android:id="@+id/recordMapViewrRoot"
android:layout_width="match_parent"

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
~
~ This file is part of FitoTrack
~
~ FitoTrack is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ FitoTrack is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.ShowWorkoutActivity">
<LinearLayout
android:id="@+id/showWorkoutRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp"></LinearLayout>
</ScrollView>

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
~
~ This file is part of FitoTrack
~
~ FitoTrack is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ FitoTrack is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/v1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/v1title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-black"
android:text=""
android:textAllCaps="true"
android:textColor="@color/textColorLight"
android:textStyle="bold" />
<TextView
android:id="@+id/v1value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-black"
android:text=""
android:textAllCaps="false"
android:textColor="@color/textLighterBlack"
android:textSize="24sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/v2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/v2title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-black"
android:text=""
android:textAllCaps="true"
android:textColor="@color/textColorLight"
android:textStyle="bold" />
<TextView
android:id="@+id/v2value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-black"
android:text=""
android:textAllCaps="false"
android:textColor="@color/textLighterBlack"
android:textSize="24sp"
android:textStyle="bold" />
</LinearLayout>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="204dp"
android:layout_marginEnd="207dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintGuide_begin="204dp"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>

View File

@ -1,13 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
~
~ This file is part of FitoTrack
~
~ FitoTrack is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ FitoTrack is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="?android:selectableItemBackground">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:layout_margin="15dp">
<TextView
android:id="@+id/workoutLength"
@ -26,9 +52,18 @@
</LinearLayout>
<LinearLayout
android:id="@+id/workoutPreviewRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/darker_gray"/>
</LinearLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
~
~ This file is part of FitoTrack
~
~ FitoTrack is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ FitoTrack is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/actionDeleteWorkout"
android:showAsAction="ifRoom"
android:title="@string/deleteWorkout" />
</menu>

View File

@ -1,7 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
~
~ This file is part of FitoTrack
~
~ FitoTrack is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ FitoTrack is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="colorPrimary">#FB8C00</color>
<color name="colorPrimaryDark">#EF6C00</color>
<color name="colorPrimaryRunning">#43A047</color>
<color name="colorPrimaryDarkRunning">#2E7D32</color>
<color name="colorPrimaryBicycling">#523220</color>
<color name="colorPrimaryDarkBicycling">#462A1D</color>
<color name="colorAccent">#4CAF50</color>
<color name="textLighterBlack">#C8000000</color>
<color name="textColorLight">#C8414141</color>
</resources>

View File

@ -21,4 +21,5 @@
<string name="app_name">FitoTrack</string>
<string name="workout_add">Add</string>
<string name="workoutStopRecording">Stop</string>
<string name="deleteWorkout">Delete</string>
</resources>

View File

@ -1,11 +1,45 @@
<!--
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
~
~ This file is part of FitoTrack
~
~ FitoTrack is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ FitoTrack is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Material.Light">
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:colorPrimary">@color/colorPrimary</item>
<item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="android:colorAccent">@color/colorAccent</item>
</style>
<!-- Base application theme. -->
<style name="Running" parent="AppTheme">
<!-- Customize your theme here. -->
<item name="android:colorPrimary">@color/colorPrimaryRunning</item>
<item name="android:colorPrimaryDark">@color/colorPrimaryDarkRunning</item>
</style>
<!-- Base application theme. -->
<style name="Bicycling" parent="AppTheme">
<!-- Customize your theme here. -->
<item name="android:colorPrimary">@color/colorPrimaryBicycling</item>
<item name="android:colorPrimaryDark">@color/colorPrimaryDarkBicycling</item>
</style>
</resources>