mirror of
https://github.com/russok/FitoTrack.git
synced 2025-10-29 00:32:11 -07:00
Delete Workouts, Recording Information, Automtic pause, GPX-library added for future export
This commit is contained in:
parent
7e406db592
commit
c7ddf0ed58
17
NOTICE.md
17
NOTICE.md
@ -15,3 +15,20 @@
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
<https://github.com/jenetics/jpx>
|
||||
|
||||
Copyright 2016-2019 Franz Wilhelmstötter
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@ -23,6 +23,7 @@ allprojects {
|
||||
dependencies {
|
||||
repositories {
|
||||
jcenter()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -44,6 +45,10 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = '1.8'
|
||||
targetCompatibility = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -59,6 +64,7 @@ dependencies {
|
||||
implementation 'org.mapsforge:mapsforge-map-android:0.11.0'
|
||||
implementation 'com.caverock:androidsvg:1.3'
|
||||
implementation 'net.sf.kxml:kxml2:2.3.0'
|
||||
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
|
||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
|
||||
BIN
app/libs/jpx-1.6.0.jar
Normal file
BIN
app/libs/jpx-1.6.0.jar
Normal file
Binary file not shown.
@ -19,6 +19,7 @@
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="de.tadris.fitness">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
@ -31,11 +32,16 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".activity.ShowWorkoutActivity"></activity>
|
||||
<activity android:name=".activity.RecordWorkoutActivity" />
|
||||
<activity android:name=".activity.ListWorkoutsActivity" />
|
||||
<activity android:name=".activity.LauncherActivity">
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity android:name=".activity.ShowWorkoutActivity"
|
||||
android:screenOrientation="portrait"/>
|
||||
<activity android:name=".activity.RecordWorkoutActivity"
|
||||
android:screenOrientation="portrait"/>
|
||||
<activity android:name=".activity.ListWorkoutsActivity"
|
||||
android:screenOrientation="portrait"/>
|
||||
<activity android:name=".activity.LauncherActivity"
|
||||
android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
||||
@ -21,10 +21,7 @@ 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;
|
||||
@ -44,17 +41,13 @@ public class Instance {
|
||||
|
||||
public AppDatabase db;
|
||||
public LocationListener locationListener;
|
||||
public UserPreferences userPreferences;
|
||||
|
||||
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);
|
||||
userPreferences= new UserPreferences();
|
||||
}
|
||||
}
|
||||
|
||||
29
app/src/main/java/de/tadris/fitness/UserPreferences.java
Normal file
29
app/src/main/java/de/tadris/fitness/UserPreferences.java
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class UserPreferences {
|
||||
|
||||
/**
|
||||
* Weight in kg
|
||||
*/
|
||||
public float weight= 80;
|
||||
|
||||
}
|
||||
@ -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].getDuration()));
|
||||
holder.timeText.setText(UnitUtils.getHourMinuteTime(workouts[position].duration));
|
||||
holder.root.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.util.TypedValue;
|
||||
|
||||
public class FitoTrackActivity extends Activity {
|
||||
|
||||
|
||||
|
||||
protected int getThemePrimaryColor() {
|
||||
final TypedValue value = new TypedValue ();
|
||||
getTheme().resolveAttribute (android.R.attr.colorPrimary, value, true);
|
||||
return value.data;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -23,6 +23,7 @@ import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.R;
|
||||
|
||||
public class LauncherActivity extends Activity {
|
||||
@ -31,7 +32,21 @@ public class LauncherActivity extends Activity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume(){
|
||||
super.onResume();
|
||||
init();
|
||||
}
|
||||
|
||||
void init(){
|
||||
Instance.getInstance(this);
|
||||
start();
|
||||
}
|
||||
|
||||
void start(){
|
||||
startActivity(new Intent(this, ListWorkoutsActivity.class));
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,19 +20,27 @@
|
||||
package de.tadris.fitness.activity;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import org.mapsforge.core.graphics.Paint;
|
||||
import org.mapsforge.core.graphics.Style;
|
||||
import org.mapsforge.core.model.LatLong;
|
||||
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
|
||||
import org.mapsforge.map.android.view.MapView;
|
||||
import org.mapsforge.map.layer.download.TileDownloadLayer;
|
||||
import org.mapsforge.map.layer.overlay.Polyline;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.R;
|
||||
@ -40,28 +48,96 @@ import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.location.LocationListener;
|
||||
import de.tadris.fitness.location.WorkoutRecorder;
|
||||
import de.tadris.fitness.map.MapManager;
|
||||
import de.tadris.fitness.util.ThemeManager;
|
||||
import de.tadris.fitness.util.UnitUtils;
|
||||
|
||||
public class RecordWorkoutActivity extends Activity implements LocationListener.LocationChangeListener {
|
||||
public class RecordWorkoutActivity extends FitoTrackActivity implements LocationListener.LocationChangeListener {
|
||||
|
||||
public static String ACTIVITY= Workout.WORKOUT_TYPE_RUNNING;
|
||||
|
||||
MapView mapView;
|
||||
TileDownloadLayer downloadLayer;
|
||||
WorkoutRecorder recorder;
|
||||
Polyline polyline;
|
||||
List<LatLong> latLongList= new ArrayList<>();
|
||||
InfoViewHolder[] infoViews= new InfoViewHolder[4];
|
||||
TextView timeView, gpsStatusView;
|
||||
boolean isResumed= false;
|
||||
private Handler mHandler= new Handler();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setTheme(ThemeManager.getThemeByWorkoutType(ACTIVITY));
|
||||
setContentView(R.layout.activity_record_workout);
|
||||
|
||||
this.mapView= new MapView(this);
|
||||
|
||||
downloadLayer= MapManager.setupMap(mapView);
|
||||
setupMap();
|
||||
|
||||
((ViewGroup)findViewById(R.id.recordMapViewrRoot)).addView(mapView);
|
||||
|
||||
checkPermissions();
|
||||
|
||||
recorder= new WorkoutRecorder(this, Workout.WORKOUT_TYPE_RUNNING);
|
||||
recorder= new WorkoutRecorder(this, ACTIVITY);
|
||||
recorder.start();
|
||||
|
||||
infoViews[0]= new InfoViewHolder((TextView) findViewById(R.id.recordInfo1Title), (TextView) findViewById(R.id.recordInfo1Value));
|
||||
infoViews[1]= new InfoViewHolder((TextView) findViewById(R.id.recordInfo2Title), (TextView) findViewById(R.id.recordInfo2Value));
|
||||
infoViews[2]= new InfoViewHolder((TextView) findViewById(R.id.recordInfo3Title), (TextView) findViewById(R.id.recordInfo3Value));
|
||||
infoViews[3]= new InfoViewHolder((TextView) findViewById(R.id.recordInfo4Title), (TextView) findViewById(R.id.recordInfo4Value));
|
||||
timeView= findViewById(R.id.recordTime);
|
||||
|
||||
updateDescription();
|
||||
|
||||
startUpdater();
|
||||
}
|
||||
|
||||
private void setupMap(){
|
||||
this.mapView= new MapView(this);
|
||||
downloadLayer= MapManager.setupMap(mapView);
|
||||
}
|
||||
|
||||
private void updateLine(){
|
||||
if(polyline != null){
|
||||
mapView.getLayerManager().getLayers().remove(polyline);
|
||||
}
|
||||
Paint p= AndroidGraphicFactory.INSTANCE.createPaint();
|
||||
p.setColor(getThemePrimaryColor());
|
||||
p.setStrokeWidth(20);
|
||||
p.setStyle(Style.STROKE);
|
||||
polyline= new Polyline(p, AndroidGraphicFactory.INSTANCE);
|
||||
polyline.setPoints(latLongList);
|
||||
mapView.addLayer(polyline);
|
||||
}
|
||||
|
||||
private void startUpdater(){
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try{
|
||||
while (recorder.isActive()){
|
||||
Thread.sleep(1000);
|
||||
if(isResumed){
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateDescription();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}catch (InterruptedException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void updateDescription(){
|
||||
timeView.setText(UnitUtils.getHourMinuteSecondTime(recorder.getDuration()));
|
||||
infoViews[0].setText(getString(R.string.workoutDistance), UnitUtils.getDistance(recorder.getDistance()));
|
||||
infoViews[1].setText(getString(R.string.workoutBurnedEnergy), recorder.getCalories() + " kcal");
|
||||
infoViews[2].setText(getString(R.string.workoutAvgSpeed), UnitUtils.getSpeed(recorder.getAvgSpeed()));
|
||||
infoViews[3].setText(getString(R.string.workoutPauseDuration), UnitUtils.getHourMinuteSecondTime(recorder.getPauseDuration()));
|
||||
}
|
||||
|
||||
private void stopAndSave(){
|
||||
@ -92,7 +168,10 @@ public class RecordWorkoutActivity extends Activity implements LocationListener.
|
||||
|
||||
@Override
|
||||
public void onLocationChange(Location location) {
|
||||
mapView.getModel().mapViewPosition.animateTo(LocationListener.locationToLatLong(location));
|
||||
LatLong latLong= LocationListener.locationToLatLong(location);
|
||||
mapView.getModel().mapViewPosition.animateTo(latLong);
|
||||
latLongList.add(latLong);
|
||||
updateLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -108,12 +187,14 @@ public class RecordWorkoutActivity extends Activity implements LocationListener.
|
||||
super.onPause();
|
||||
downloadLayer.onPause();
|
||||
Instance.getInstance(this).locationListener.unregisterLocationChangeListeners(this);
|
||||
isResumed= false;
|
||||
}
|
||||
|
||||
public void onResume(){
|
||||
super.onResume();
|
||||
downloadLayer.onResume();
|
||||
Instance.getInstance(this).locationListener.registerLocationChangeListeners(this);
|
||||
isResumed= true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -133,5 +214,19 @@ public class RecordWorkoutActivity extends Activity implements LocationListener.
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public static class InfoViewHolder{
|
||||
TextView titleView, valueView;
|
||||
|
||||
public InfoViewHolder(TextView titleView, TextView valueView) {
|
||||
this.titleView = titleView;
|
||||
this.valueView = valueView;
|
||||
}
|
||||
|
||||
void setText(String title, String value){
|
||||
this.titleView.setText(title);
|
||||
this.valueView.setText(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -19,8 +19,10 @@
|
||||
|
||||
package de.tadris.fitness.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@ -31,14 +33,25 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.github.mikephil.charting.charts.LineChart;
|
||||
import com.github.mikephil.charting.components.Description;
|
||||
import com.github.mikephil.charting.data.Entry;
|
||||
import com.github.mikephil.charting.data.LineData;
|
||||
import com.github.mikephil.charting.data.LineDataSet;
|
||||
import com.github.mikephil.charting.highlight.Highlight;
|
||||
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
|
||||
|
||||
import org.mapsforge.core.graphics.Paint;
|
||||
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 org.mapsforge.map.layer.overlay.FixedPixelCircle;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@ -46,13 +59,14 @@ 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.WorkoutManager;
|
||||
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 {
|
||||
public class ShowWorkoutActivity extends FitoTrackActivity {
|
||||
static Workout selectedWorkout;
|
||||
|
||||
List<WorkoutSample> samples;
|
||||
@ -61,6 +75,7 @@ public class ShowWorkoutActivity extends Activity {
|
||||
Resources.Theme theme;
|
||||
MapView map;
|
||||
TileDownloadLayer downloadLayer;
|
||||
FixedPixelCircle highlightingCircle;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -68,7 +83,7 @@ public class ShowWorkoutActivity extends Activity {
|
||||
|
||||
workout= selectedWorkout;
|
||||
samples= Arrays.asList(Instance.getInstance(this).db.workoutDao().getAllSamplesOfWorkout(workout.id));
|
||||
setTheme(ThemeManager.getThemeByWorkout(workout, this));
|
||||
setTheme(ThemeManager.getThemeByWorkout(workout));
|
||||
setContentView(R.layout.activity_show_workout);
|
||||
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
@ -77,28 +92,31 @@ public class ShowWorkoutActivity extends Activity {
|
||||
|
||||
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)));
|
||||
addTitle(getString(R.string.workoutTime));
|
||||
addKeyValue(getString(R.string.workoutDate), getDate());
|
||||
addKeyValue(getString(R.string.workoutDuration), UnitUtils.getHourMinuteSecondTime(workout.duration),
|
||||
getString(R.string.workoutPauseDuration), UnitUtils.getHourMinuteSecondTime(workout.pauseDuration));
|
||||
addKeyValue(getString(R.string.workoutStartTime), SimpleDateFormat.getTimeInstance().format(new Date(workout.start)),
|
||||
getString(R.string.workoutEndTime), SimpleDateFormat.getTimeInstance().format(new Date(workout.end)));
|
||||
|
||||
addKeyValue("Distanz", UnitUtils.getDistance(workout.length), "Pace", UnitUtils.round(workout.avgPace, 1) + " min/km");
|
||||
addKeyValue(getString(R.string.workoutDistance), UnitUtils.getDistance(workout.length), getString(R.string.workoutPace), UnitUtils.getPace(workout.avgPace));
|
||||
|
||||
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");
|
||||
addTitle(getString(R.string.workoutRoute));
|
||||
|
||||
addMap();
|
||||
|
||||
addTitle(getString(R.string.workoutSpeed));
|
||||
|
||||
addKeyValue(getString(R.string.workoutAvgSpeed), UnitUtils.getSpeed(workout.avgSpeed),
|
||||
getString(R.string.workoutTopSpeed), UnitUtils.getSpeed(workout.topSpeed));
|
||||
|
||||
addSpeedDiagram();
|
||||
|
||||
addTitle(getString(R.string.workoutBurnedEnergy));
|
||||
addKeyValue(getString(R.string.workoutTotalEnergy), workout.calorie + " kcal",
|
||||
getString(R.string.workoutEnergyConsumption), UnitUtils.getPace((double)workout.calorie / workout.length / 1000));
|
||||
|
||||
|
||||
}
|
||||
|
||||
String getDate(){
|
||||
@ -113,6 +131,7 @@ public class ShowWorkoutActivity extends Activity {
|
||||
textView.setTextColor(getThemePrimaryColor());
|
||||
textView.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
textView.setAllCaps(true);
|
||||
textView.setPadding(0, 20, 0, 0);
|
||||
|
||||
root.addView(textView);
|
||||
}
|
||||
@ -137,8 +156,62 @@ public class ShowWorkoutActivity extends Activity {
|
||||
root.addView(v);
|
||||
}
|
||||
|
||||
void addDiagram(){
|
||||
void addSpeedDiagram(){
|
||||
LineChart chart= new LineChart(this);
|
||||
|
||||
WorkoutManager.roundSpeedValues(samples);
|
||||
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
for (WorkoutSample sample : samples) {
|
||||
// turn your data into Entry objects
|
||||
Entry e= new Entry((float)(sample.relativeTime) / 1000f / 60f, (float)sample.tmpRoundedSpeed*3.6f);
|
||||
entries.add(e);
|
||||
sample.tmpEntry= e;
|
||||
}
|
||||
|
||||
LineDataSet dataSet = new LineDataSet(entries, "Speed"); // add entries to dataset // TODO: localisatoin
|
||||
dataSet.setColor(getThemePrimaryColor());
|
||||
dataSet.setValueTextColor(getThemePrimaryColor());
|
||||
dataSet.setDrawCircles(false);
|
||||
dataSet.setLineWidth(4);
|
||||
dataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER);
|
||||
|
||||
Description description= new Description();
|
||||
description.setText("min - km/h");
|
||||
|
||||
LineData lineData = new LineData(dataSet);
|
||||
chart.setData(lineData);
|
||||
chart.setScaleEnabled(false);
|
||||
chart.setDescription(description);
|
||||
chart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
|
||||
@Override
|
||||
public void onValueSelected(Entry e, Highlight h) {
|
||||
onNothingSelected();
|
||||
Paint p= AndroidGraphicFactory.INSTANCE.createPaint();
|
||||
p.setColor(Color.BLUE);
|
||||
highlightingCircle= new FixedPixelCircle(getSamplebyTime(e).toLatLong(), 10, p, null);
|
||||
map.addLayer(highlightingCircle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected() {
|
||||
if(highlightingCircle != null){
|
||||
map.getLayerManager().getLayers().remove(highlightingCircle);
|
||||
}
|
||||
}
|
||||
});
|
||||
chart.invalidate();
|
||||
|
||||
root.addView(chart, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getWindowManager().getDefaultDisplay().getWidth()*3/4));
|
||||
}
|
||||
|
||||
WorkoutSample getSamplebyTime(Entry entry){
|
||||
for(WorkoutSample sample : samples){
|
||||
if(sample.tmpEntry.equalTo(entry)){
|
||||
return sample;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void addMap(){
|
||||
@ -165,12 +238,14 @@ public class ShowWorkoutActivity extends Activity {
|
||||
|
||||
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;
|
||||
|
||||
Paint pGreen= AndroidGraphicFactory.INSTANCE.createPaint();
|
||||
pGreen.setColor(Color.GREEN);
|
||||
map.addLayer(new FixedPixelCircle(samples.get(0).toLatLong(), 20, pGreen, null));
|
||||
Paint pRed= AndroidGraphicFactory.INSTANCE.createPaint();
|
||||
pRed.setColor(Color.RED);
|
||||
map.addLayer(new FixedPixelCircle(samples.get(samples.size()-1).toLatLong(), 20, pRed, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -198,11 +273,29 @@ public class ShowWorkoutActivity extends Activity {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void deleteWorkout(){
|
||||
Instance.getInstance(this).db.workoutDao().deleteWorkout(workout);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void showDeleteDialog(){
|
||||
new AlertDialog.Builder(this).setTitle(R.string.deleteWorkout)
|
||||
.setMessage(R.string.deleteWorkoutMessage)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
deleteWorkout();
|
||||
}
|
||||
})
|
||||
.create().show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if(id == R.id.actionDeleteWorkout){
|
||||
// TODO: delete workout
|
||||
showDeleteDialog();
|
||||
return true;
|
||||
}else if(id == android.R.id.home){
|
||||
finish();
|
||||
@ -211,18 +304,4 @@ public class ShowWorkoutActivity extends Activity {
|
||||
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();
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ package de.tadris.fitness.data;
|
||||
import androidx.room.Database;
|
||||
import androidx.room.RoomDatabase;
|
||||
|
||||
@Database(version = 3, entities = {Workout.class, WorkoutSample.class})
|
||||
@Database(version = 1, entities = {Workout.class, WorkoutSample.class})
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
public abstract WorkoutDao workoutDao();
|
||||
}
|
||||
|
||||
@ -36,6 +36,10 @@ public class Workout{
|
||||
public long start;
|
||||
public long end;
|
||||
|
||||
public long duration;
|
||||
|
||||
public long pauseDuration;
|
||||
|
||||
/**
|
||||
* Length of workout in meters
|
||||
*/
|
||||
@ -60,9 +64,5 @@ public class Workout{
|
||||
|
||||
public int calorie;
|
||||
|
||||
public long getDuration(){
|
||||
return end - start;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -20,8 +20,10 @@
|
||||
package de.tadris.fitness.data;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
@Dao
|
||||
public interface WorkoutDao {
|
||||
@ -29,7 +31,7 @@ public interface WorkoutDao {
|
||||
@Query("SELECT * FROM workout_sample WHERE workout_id = :workout_id")
|
||||
WorkoutSample[] getAllSamplesOfWorkout(long workout_id);
|
||||
|
||||
@Query("SELECT * FROM workout")
|
||||
@Query("SELECT * FROM workout ORDER BY start DESC")
|
||||
Workout[] getWorkouts();
|
||||
|
||||
@Insert
|
||||
@ -38,7 +40,11 @@ public interface WorkoutDao {
|
||||
@Insert
|
||||
void insertWorkout(Workout workout);
|
||||
|
||||
@Query("SELECT * FROM workout ORDER BY start DESC LIMIT 1")
|
||||
Workout findLastWorkout();
|
||||
@Delete
|
||||
void deleteWorkout(Workout workout);
|
||||
|
||||
@Update
|
||||
void updateWorkout(Workout workout);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -37,13 +37,15 @@ public class WorkoutManager {
|
||||
// Calculating values
|
||||
double length= 0;
|
||||
for(int i= 1; i < samples.size(); i++){
|
||||
length+= samples.get(i - 1).toLatLong().sphericalDistance(samples.get(i).toLatLong());
|
||||
double sampleLength= samples.get(i - 1).toLatLong().sphericalDistance(samples.get(i).toLatLong());
|
||||
long timeDiff= (samples.get(i).relativeTime - samples.get(i - 1).relativeTime) / 1000;
|
||||
length+= sampleLength;
|
||||
samples.get(i).speed= Math.abs(sampleLength / timeDiff);
|
||||
}
|
||||
workout.length= (int)length;
|
||||
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
|
||||
workout.avgSpeed= ((double) workout.length) / ((double) workout.duration / 1000);
|
||||
workout.avgPace= ((double)workout.duration / 1000 / 60) / ((double) workout.length / 1000);
|
||||
workout.calorie= CalorieCalculator.calculateCalories(workout, Instance.getInstance(context).userPreferences.weight);
|
||||
|
||||
// Setting workoutId in the samples
|
||||
int i= 0;
|
||||
@ -65,4 +67,17 @@ public class WorkoutManager {
|
||||
|
||||
}
|
||||
|
||||
public static void roundSpeedValues(List<WorkoutSample> samples){
|
||||
for(int i= 0; i < samples.size(); i++){
|
||||
WorkoutSample sample= samples.get(i);
|
||||
if(i == 0){
|
||||
sample.tmpRoundedSpeed= (sample.speed+samples.get(i+1).speed) / 2;
|
||||
}else if(i == samples.size()-1){
|
||||
sample.tmpRoundedSpeed= (sample.speed+samples.get(i-1).speed) / 2;
|
||||
}else{
|
||||
sample.tmpRoundedSpeed= (sample.speed+samples.get(i-1).speed+samples.get(i+1).speed) / 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -22,8 +22,11 @@ package de.tadris.fitness.data;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.ForeignKey;
|
||||
import androidx.room.Ignore;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import com.github.mikephil.charting.data.Entry;
|
||||
|
||||
import org.mapsforge.core.model.LatLong;
|
||||
|
||||
import static androidx.room.ForeignKey.CASCADE;
|
||||
@ -42,14 +45,26 @@ public class WorkoutSample{
|
||||
@ColumnInfo(name = "workout_id")
|
||||
public long workoutId;
|
||||
|
||||
public long time;
|
||||
public long absoluteTime;
|
||||
|
||||
public long relativeTime;
|
||||
|
||||
public double lat;
|
||||
|
||||
public double lon;
|
||||
|
||||
public double elevation;
|
||||
|
||||
public double relativeElevation;
|
||||
|
||||
public double speed;
|
||||
|
||||
@Ignore
|
||||
public Entry tmpEntry;
|
||||
|
||||
@Ignore
|
||||
public double tmpRoundedSpeed;
|
||||
|
||||
public LatLong toLatLong(){
|
||||
return new LatLong(lat, lon);
|
||||
}
|
||||
|
||||
@ -124,7 +124,7 @@ public class LocationListener implements android.location.LocationListener {
|
||||
|
||||
boolean result = false;
|
||||
for (String provider : this.locationManager.getProviders(true)) {
|
||||
if (LocationManager.GPS_PROVIDER.equals(provider) || LocationManager.NETWORK_PROVIDER.equals(provider)) {
|
||||
if (LocationManager.GPS_PROVIDER.equals(provider)) {
|
||||
result = true;
|
||||
if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
return;
|
||||
|
||||
@ -32,17 +32,33 @@ import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.data.WorkoutManager;
|
||||
import de.tadris.fitness.data.WorkoutSample;
|
||||
import de.tadris.fitness.util.CalorieCalculator;
|
||||
|
||||
public class WorkoutRecorder implements LocationListener.LocationChangeListener {
|
||||
|
||||
private static final int MIN_DISTANCE= 5;
|
||||
private static int getMinDistance(String workoutType){
|
||||
switch (workoutType){
|
||||
case Workout.WORKOUT_TYPE_HIKING:
|
||||
case Workout.WORKOUT_TYPE_RUNNING:
|
||||
return 8;
|
||||
case Workout.WORKOUT_TYPE_CYCLING:
|
||||
return 15;
|
||||
default: return 10;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int PAUSE_TIME= 10000;
|
||||
|
||||
private Context context;
|
||||
private Workout workout;
|
||||
private RecordingState state;
|
||||
private List<WorkoutSample> samples= new ArrayList<>();
|
||||
private final List<WorkoutSample> samples= new ArrayList<>();
|
||||
private long time= 0;
|
||||
private long pauseTime= 0;
|
||||
private long lastResume;
|
||||
private long lastPause= 0;
|
||||
private long lastSampleTime= 0;
|
||||
private double distance= 0;
|
||||
|
||||
public WorkoutRecorder(Context context, String workoutType) {
|
||||
this.context= context;
|
||||
@ -54,9 +70,11 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
|
||||
|
||||
public void start(){
|
||||
if(state == RecordingState.IDLE){
|
||||
Log.i("Recorder", "");
|
||||
Log.i("Recorder", "Start");
|
||||
workout.start= System.currentTimeMillis();
|
||||
resume();
|
||||
Instance.getInstance(context).locationListener.registerLocationChangeListeners(this);
|
||||
startWatchdog();
|
||||
}else if(state == RecordingState.PAUSED){
|
||||
resume();
|
||||
}else if(state != RecordingState.RUNNING){
|
||||
@ -64,52 +82,160 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isActive(){
|
||||
return state == RecordingState.RUNNING || state == RecordingState.PAUSED;
|
||||
}
|
||||
|
||||
private void startWatchdog(){
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (isActive()){
|
||||
synchronized (samples){
|
||||
if(samples.size() > 2){
|
||||
WorkoutSample lastSample= samples.get(samples.size()-1);
|
||||
if(System.currentTimeMillis() - lastSampleTime > PAUSE_TIME){
|
||||
if(state == RecordingState.RUNNING){
|
||||
pause();
|
||||
}
|
||||
}else{
|
||||
if(state == RecordingState.PAUSED){
|
||||
resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Thread.sleep(5000);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void resume(){
|
||||
Log.i("Recorder", "Resume");
|
||||
state= RecordingState.RUNNING;
|
||||
lastResume= System.currentTimeMillis();
|
||||
Instance.getInstance(context).locationListener.registerLocationChangeListeners(this);
|
||||
if(lastPause != 0){
|
||||
pauseTime+= System.currentTimeMillis() - lastPause;
|
||||
}
|
||||
}
|
||||
|
||||
public void pause(){
|
||||
if(state == RecordingState.RUNNING){
|
||||
Log.i("Recorder", "Pause");
|
||||
state= RecordingState.PAUSED;
|
||||
time+= System.currentTimeMillis() - lastResume;
|
||||
Instance.getInstance(context).locationListener.unregisterLocationChangeListeners(this);
|
||||
lastPause= System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop(){
|
||||
Log.i("Recorder", "Stop");
|
||||
if(state == RecordingState.PAUSED){
|
||||
resume();
|
||||
}
|
||||
pause();
|
||||
workout.end= System.currentTimeMillis();
|
||||
workout.duration= time;
|
||||
workout.pauseDuration= pauseTime;
|
||||
state= RecordingState.STOPPED;
|
||||
Instance.getInstance(context).locationListener.unregisterLocationChangeListeners(this);
|
||||
}
|
||||
|
||||
public void save(){
|
||||
if(state != RecordingState.STOPPED){
|
||||
throw new IllegalStateException("Cannot save recording, recorder was not stopped. state = " + state);
|
||||
}
|
||||
WorkoutManager.insertWorkout(context, workout, samples);
|
||||
Log.i("Recorder", "Save");
|
||||
synchronized (samples){
|
||||
WorkoutManager.insertWorkout(context, workout, samples);
|
||||
}
|
||||
}
|
||||
|
||||
public int getSampleCount(){
|
||||
return samples.size();
|
||||
synchronized (samples){
|
||||
return samples.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChange(Location location) {
|
||||
if(state == RecordingState.RUNNING){
|
||||
if(isActive()){
|
||||
double distance= 0;
|
||||
if(getSampleCount() > 0){
|
||||
WorkoutSample lastSample= samples.get(samples.size() - 1);
|
||||
if(LocationListener.locationToLatLong(location).sphericalDistance(new LatLong(lastSample.lat, lastSample.lon)) < MIN_DISTANCE){
|
||||
return;
|
||||
synchronized (samples){
|
||||
WorkoutSample lastSample= samples.get(samples.size() - 1);
|
||||
distance= LocationListener.locationToLatLong(location).sphericalDistance(new LatLong(lastSample.lat, lastSample.lon));
|
||||
long timediff= lastSample.absoluteTime - location.getTime();
|
||||
if(distance < getMinDistance(workout.workoutType) && timediff < 500){
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
WorkoutSample sample= new WorkoutSample();
|
||||
sample.lat= location.getLatitude();
|
||||
sample.lon= location.getLongitude();
|
||||
sample.speed= location.getSpeed();
|
||||
sample.time= location.getTime();
|
||||
samples.add(sample);
|
||||
lastSampleTime= System.currentTimeMillis();
|
||||
if(state == RecordingState.RUNNING && location.getTime() > workout.start){
|
||||
if(samples.size() == 2){
|
||||
lastResume= System.currentTimeMillis();
|
||||
workout.start= System.currentTimeMillis();
|
||||
lastPause= 0;
|
||||
time= 0;
|
||||
pauseTime= 0;
|
||||
this.distance= 0;
|
||||
}
|
||||
this.distance+= distance;
|
||||
WorkoutSample sample= new WorkoutSample();
|
||||
sample.lat= location.getLatitude();
|
||||
sample.lon= location.getLongitude();
|
||||
sample.elevation= location.getAltitude();
|
||||
sample.relativeElevation= 0.0;
|
||||
sample.speed= location.getSpeed();
|
||||
sample.relativeTime= location.getTime() - workout.start - pauseTime;
|
||||
sample.absoluteTime= location.getTime();
|
||||
synchronized (samples){
|
||||
samples.add(sample);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance in meters
|
||||
*/
|
||||
public int getDistance(){
|
||||
return (int)distance;
|
||||
}
|
||||
|
||||
public int getCalories(){
|
||||
workout.avgSpeed= getAvgSpeed();
|
||||
return CalorieCalculator.calculateCalories(workout, Instance.getInstance(context).userPreferences.weight);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return avgSpeed in m/s
|
||||
*/
|
||||
public double getAvgSpeed(){
|
||||
return distance / (double)(getDuration() / 1000);
|
||||
}
|
||||
|
||||
public long getPauseDuration(){
|
||||
if(state == RecordingState.PAUSED){
|
||||
return pauseTime + (System.currentTimeMillis() - lastPause);
|
||||
}else{
|
||||
return pauseTime;
|
||||
}
|
||||
}
|
||||
|
||||
public long getDuration(){
|
||||
if(state == RecordingState.RUNNING){
|
||||
return time + (System.currentTimeMillis() - lastResume);
|
||||
}else{
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -30,13 +30,15 @@ public class CalorieCalculator {
|
||||
* @return calories burned
|
||||
*/
|
||||
public static int calculateCalories(Workout workout, double weight){
|
||||
int mins= (int)(workout.getDuration() / 1000 / 60);
|
||||
double mins= (double)(workout.duration / 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 }
|
||||
*
|
||||
* workoutType and avgSpeed of workout have to be set
|
||||
*
|
||||
* @param workout
|
||||
* @return MET
|
||||
*/
|
||||
|
||||
56
app/src/main/java/de/tadris/fitness/util/GpxExporter.java
Normal file
56
app/src/main/java/de/tadris/fitness/util/GpxExporter.java
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.Instance;
|
||||
import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.data.WorkoutSample;
|
||||
import io.jenetics.jpx.GPX;
|
||||
import io.jenetics.jpx.Track;
|
||||
import io.jenetics.jpx.TrackSegment;
|
||||
|
||||
public class GpxExporter {
|
||||
|
||||
public static void exportWorkout(Context context, Workout workout){
|
||||
GPX.Builder builder= GPX.builder();
|
||||
|
||||
builder.addTrack(toTrack(context, workout));
|
||||
|
||||
}
|
||||
|
||||
public static Track toTrack(Context context, Workout workout){
|
||||
Track.Builder track= Track.builder();
|
||||
TrackSegment.Builder segment= TrackSegment.builder();
|
||||
|
||||
WorkoutSample[] samples= Instance.getInstance(context).db.workoutDao().getAllSamplesOfWorkout(workout.id);
|
||||
for(WorkoutSample sample : samples){
|
||||
segment.addPoint(p -> p.lat(sample.lat).lon(sample.lon).ele(sample.elevation).speed(sample.speed).time(sample.absoluteTime));
|
||||
}
|
||||
|
||||
track.addSegment(segment.build());
|
||||
track.src("FitoTrack");
|
||||
track.type(workout.workoutType);
|
||||
|
||||
return track.build();
|
||||
}
|
||||
|
||||
}
|
||||
@ -19,21 +19,22 @@
|
||||
|
||||
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){
|
||||
public static int getThemeByWorkoutType(String type){
|
||||
switch (type){
|
||||
case Workout.WORKOUT_TYPE_RUNNING: return R.style.Running;
|
||||
case Workout.WORKOUT_TYPE_CYCLING: return R.style.Bicycling;
|
||||
default: return R.style.AppTheme;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getThemeByWorkout(Workout workout){
|
||||
return getThemeByWorkoutType(workout.workoutType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -34,13 +34,44 @@ public class UnitUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getHourMinuteSecondTime(long time){
|
||||
long totalSeks= time / 1000;
|
||||
long totalMins= totalSeks / 60;
|
||||
long hours= totalMins / 60;
|
||||
long mins= totalMins % 60;
|
||||
long seks= totalSeks % 60;
|
||||
String minStr= (mins < 10 ? "0" : "") + mins;
|
||||
String sekStr= (seks < 10 ? "0" : "") + seks;
|
||||
return hours + ":" + minStr + ":" + sekStr;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param pace Pace in min/km
|
||||
* @return Pace
|
||||
*/
|
||||
public static String getPace(double pace){
|
||||
// TODO: use preferred unit chosen by user
|
||||
return round(pace, 1) + " min/km";
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param consumption consumption in kcal/km
|
||||
* @return
|
||||
*/
|
||||
public static String getRelativeEnergyConsumption(double consumption){
|
||||
// TODO: use preferred unit chosen by user
|
||||
return round(consumption, 2) + " kcal/km";
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param distance Distance in meters
|
||||
* @return String in preferred unit
|
||||
*/
|
||||
public static String getDistance(int distance){
|
||||
// TODO: use preferred unit by user
|
||||
// TODO: use preferred unit chosen by user
|
||||
if(distance >= 1000){
|
||||
return getDistanceInKilometers((double)distance);
|
||||
}else{
|
||||
@ -54,7 +85,7 @@ public class UnitUtils {
|
||||
* @return speed in km/h
|
||||
*/
|
||||
public static String getSpeed(double speed){
|
||||
// TODO: use preferred unit by user
|
||||
// TODO: use preferred unit chosen by user
|
||||
return round(speed*3.6, 1) + " km/h";
|
||||
}
|
||||
|
||||
|
||||
@ -18,8 +18,17 @@
|
||||
~ 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"
|
||||
<FrameLayout 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.LauncherActivity" />
|
||||
android:theme="@style/AppThemeNoActionbar"
|
||||
tools:context=".activity.LauncherActivity">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView2"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center"
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
</FrameLayout>
|
||||
@ -19,6 +19,7 @@
|
||||
-->
|
||||
|
||||
<RelativeLayout 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"
|
||||
@ -30,13 +31,6 @@
|
||||
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"
|
||||
@ -46,6 +40,14 @@
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/location_marker" />
|
||||
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
@ -58,11 +60,132 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:id="@+id/recordTime"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="20sp"
|
||||
android:text="TextView" />
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:text="0:44:08"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline2"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recordInfo1Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/workoutDistance"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recordInfo1Value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="2,06 km"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recordInfo2Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/workoutBurnedEnergy"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recordInfo2Value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="30 kcal"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/linearLayout"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recordInfo3Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/workoutAvgSpeed"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recordInfo3Value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="7 km/h"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recordInfo4Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/workoutBurnedEnergy"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recordInfo4Value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="30 kcal"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
@ -98,6 +98,6 @@
|
||||
android:layout_marginEnd="207dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintGuide_begin="204dp"
|
||||
app:layout_constraintGuide_percent="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
@ -23,5 +23,5 @@
|
||||
<item
|
||||
android:id="@+id/actionDeleteWorkout"
|
||||
android:showAsAction="ifRoom"
|
||||
android:title="@string/deleteWorkout" />
|
||||
android:title="@string/delete" />
|
||||
</menu>
|
||||
@ -21,5 +21,26 @@
|
||||
<string name="app_name">FitoTrack</string>
|
||||
<string name="workout_add">Add</string>
|
||||
<string name="workoutStopRecording">Stop</string>
|
||||
<string name="deleteWorkout">Delete</string>
|
||||
<string name="delete">Delete</string>
|
||||
|
||||
<string name="workoutTime">Time</string>
|
||||
<string name="workoutDate">Date</string>
|
||||
<string name="workoutDuration">Duration</string>
|
||||
<string name="workoutPauseDuration">Pause Duration</string>
|
||||
<string name="workoutStartTime">Start Time</string>
|
||||
<string name="workoutEndTime">End Time</string>
|
||||
<string name="workoutDistance">Distance</string>
|
||||
<string name="workoutPace">Pace</string>
|
||||
<string name="workoutRoute">Route</string>
|
||||
<string name="workoutSpeed">Speed</string>
|
||||
<string name="workoutAvgSpeed">Avg. Speed</string>
|
||||
<string name="workoutTopSpeed">Top Speed</string>
|
||||
<string name="workoutBurnedEnergy">Burned Energy</string>
|
||||
<string name="workoutTotalEnergy">Total Energy</string>
|
||||
<string name="workoutEnergyConsumption">Energy Consumption</string>
|
||||
|
||||
<string name="deleteWorkout">Delete Workout</string>
|
||||
<string name="deleteWorkoutMessage">Do you really want to delete the workout?</string>
|
||||
|
||||
<string name="cancel">Cancel</string>
|
||||
</resources>
|
||||
|
||||
@ -19,6 +19,14 @@
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="AppThemeNoActionbar" parent="android:Theme.Material.Light.NoActionBar">
|
||||
<!-- 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="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user