mirror of
https://github.com/russok/FitoTrack.git
synced 2025-10-29 08:42:12 -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.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
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 {
|
dependencies {
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,6 +45,10 @@ android {
|
|||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = '1.8'
|
||||||
|
targetCompatibility = '1.8'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -59,6 +64,7 @@ dependencies {
|
|||||||
implementation 'org.mapsforge:mapsforge-map-android:0.11.0'
|
implementation 'org.mapsforge:mapsforge-map-android:0.11.0'
|
||||||
implementation 'com.caverock:androidsvg:1.3'
|
implementation 'com.caverock:androidsvg:1.3'
|
||||||
implementation 'net.sf.kxml:kxml2:2.3.0'
|
implementation 'net.sf.kxml:kxml2:2.3.0'
|
||||||
|
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
|
||||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||||
testImplementation 'junit:junit:4.12'
|
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"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="de.tadris.fitness">
|
package="de.tadris.fitness">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
@ -31,11 +32,16 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme"
|
||||||
<activity android:name=".activity.ShowWorkoutActivity"></activity>
|
tools:ignore="GoogleAppIndexingWarning">
|
||||||
<activity android:name=".activity.RecordWorkoutActivity" />
|
<activity android:name=".activity.ShowWorkoutActivity"
|
||||||
<activity android:name=".activity.ListWorkoutsActivity" />
|
android:screenOrientation="portrait"/>
|
||||||
<activity android:name=".activity.LauncherActivity">
|
<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>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
|||||||
@ -21,10 +21,7 @@ package de.tadris.fitness;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.room.Room;
|
import androidx.room.Room;
|
||||||
import androidx.room.migration.Migration;
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
|
||||||
|
|
||||||
import de.tadris.fitness.data.AppDatabase;
|
import de.tadris.fitness.data.AppDatabase;
|
||||||
import de.tadris.fitness.location.LocationListener;
|
import de.tadris.fitness.location.LocationListener;
|
||||||
@ -44,17 +41,13 @@ public class Instance {
|
|||||||
|
|
||||||
public AppDatabase db;
|
public AppDatabase db;
|
||||||
public LocationListener locationListener;
|
public LocationListener locationListener;
|
||||||
|
public UserPreferences userPreferences;
|
||||||
|
|
||||||
private Instance(Context context) {
|
private Instance(Context context) {
|
||||||
db = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
|
db = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
|
||||||
.allowMainThreadQueries()
|
.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();
|
.build();
|
||||||
locationListener= new LocationListener(context);
|
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
|
@Override
|
||||||
public void onBindViewHolder(WorkoutViewHolder holder, final int position) {
|
public void onBindViewHolder(WorkoutViewHolder holder, final int position) {
|
||||||
holder.lengthText.setText(UnitUtils.getDistance(workouts[position].length));
|
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() {
|
holder.root.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
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.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import de.tadris.fitness.Instance;
|
||||||
import de.tadris.fitness.R;
|
import de.tadris.fitness.R;
|
||||||
|
|
||||||
public class LauncherActivity extends Activity {
|
public class LauncherActivity extends Activity {
|
||||||
@ -31,7 +32,21 @@ public class LauncherActivity extends Activity {
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume(){
|
||||||
|
super.onResume();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(){
|
||||||
|
Instance.getInstance(this);
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void start(){
|
||||||
startActivity(new Intent(this, ListWorkoutsActivity.class));
|
startActivity(new Intent(this, ListWorkoutsActivity.class));
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,19 +20,27 @@
|
|||||||
package de.tadris.fitness.activity;
|
package de.tadris.fitness.activity;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.core.app.ActivityCompat;
|
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.graphics.AndroidGraphicFactory;
|
||||||
import org.mapsforge.map.android.view.MapView;
|
import org.mapsforge.map.android.view.MapView;
|
||||||
import org.mapsforge.map.layer.download.TileDownloadLayer;
|
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.Instance;
|
||||||
import de.tadris.fitness.R;
|
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.LocationListener;
|
||||||
import de.tadris.fitness.location.WorkoutRecorder;
|
import de.tadris.fitness.location.WorkoutRecorder;
|
||||||
import de.tadris.fitness.map.MapManager;
|
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;
|
MapView mapView;
|
||||||
TileDownloadLayer downloadLayer;
|
TileDownloadLayer downloadLayer;
|
||||||
WorkoutRecorder recorder;
|
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
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
setTheme(ThemeManager.getThemeByWorkoutType(ACTIVITY));
|
||||||
setContentView(R.layout.activity_record_workout);
|
setContentView(R.layout.activity_record_workout);
|
||||||
|
|
||||||
this.mapView= new MapView(this);
|
setupMap();
|
||||||
|
|
||||||
downloadLayer= MapManager.setupMap(mapView);
|
|
||||||
|
|
||||||
((ViewGroup)findViewById(R.id.recordMapViewrRoot)).addView(mapView);
|
((ViewGroup)findViewById(R.id.recordMapViewrRoot)).addView(mapView);
|
||||||
|
|
||||||
checkPermissions();
|
checkPermissions();
|
||||||
|
|
||||||
recorder= new WorkoutRecorder(this, Workout.WORKOUT_TYPE_RUNNING);
|
recorder= new WorkoutRecorder(this, ACTIVITY);
|
||||||
recorder.start();
|
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(){
|
private void stopAndSave(){
|
||||||
@ -92,7 +168,10 @@ public class RecordWorkoutActivity extends Activity implements LocationListener.
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLocationChange(Location location) {
|
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
|
@Override
|
||||||
@ -108,12 +187,14 @@ public class RecordWorkoutActivity extends Activity implements LocationListener.
|
|||||||
super.onPause();
|
super.onPause();
|
||||||
downloadLayer.onPause();
|
downloadLayer.onPause();
|
||||||
Instance.getInstance(this).locationListener.unregisterLocationChangeListeners(this);
|
Instance.getInstance(this).locationListener.unregisterLocationChangeListeners(this);
|
||||||
|
isResumed= false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onResume(){
|
public void onResume(){
|
||||||
super.onResume();
|
super.onResume();
|
||||||
downloadLayer.onResume();
|
downloadLayer.onResume();
|
||||||
Instance.getInstance(this).locationListener.registerLocationChangeListeners(this);
|
Instance.getInstance(this).locationListener.registerLocationChangeListeners(this);
|
||||||
|
isResumed= true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -133,5 +214,19 @@ public class RecordWorkoutActivity extends Activity implements LocationListener.
|
|||||||
return super.onOptionsItemSelected(item);
|
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;
|
package de.tadris.fitness.activity;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Color;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@ -31,14 +33,25 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
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.BoundingBox;
|
||||||
import org.mapsforge.core.model.MapPosition;
|
import org.mapsforge.core.model.MapPosition;
|
||||||
import org.mapsforge.core.util.LatLongUtils;
|
import org.mapsforge.core.util.LatLongUtils;
|
||||||
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
|
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
|
||||||
import org.mapsforge.map.android.view.MapView;
|
import org.mapsforge.map.android.view.MapView;
|
||||||
import org.mapsforge.map.layer.download.TileDownloadLayer;
|
import org.mapsforge.map.layer.download.TileDownloadLayer;
|
||||||
|
import org.mapsforge.map.layer.overlay.FixedPixelCircle;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -46,13 +59,14 @@ import java.util.List;
|
|||||||
import de.tadris.fitness.Instance;
|
import de.tadris.fitness.Instance;
|
||||||
import de.tadris.fitness.R;
|
import de.tadris.fitness.R;
|
||||||
import de.tadris.fitness.data.Workout;
|
import de.tadris.fitness.data.Workout;
|
||||||
|
import de.tadris.fitness.data.WorkoutManager;
|
||||||
import de.tadris.fitness.data.WorkoutSample;
|
import de.tadris.fitness.data.WorkoutSample;
|
||||||
import de.tadris.fitness.map.MapManager;
|
import de.tadris.fitness.map.MapManager;
|
||||||
import de.tadris.fitness.map.WorkoutLayer;
|
import de.tadris.fitness.map.WorkoutLayer;
|
||||||
import de.tadris.fitness.util.ThemeManager;
|
import de.tadris.fitness.util.ThemeManager;
|
||||||
import de.tadris.fitness.util.UnitUtils;
|
import de.tadris.fitness.util.UnitUtils;
|
||||||
|
|
||||||
public class ShowWorkoutActivity extends Activity {
|
public class ShowWorkoutActivity extends FitoTrackActivity {
|
||||||
static Workout selectedWorkout;
|
static Workout selectedWorkout;
|
||||||
|
|
||||||
List<WorkoutSample> samples;
|
List<WorkoutSample> samples;
|
||||||
@ -61,6 +75,7 @@ public class ShowWorkoutActivity extends Activity {
|
|||||||
Resources.Theme theme;
|
Resources.Theme theme;
|
||||||
MapView map;
|
MapView map;
|
||||||
TileDownloadLayer downloadLayer;
|
TileDownloadLayer downloadLayer;
|
||||||
|
FixedPixelCircle highlightingCircle;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@ -68,7 +83,7 @@ public class ShowWorkoutActivity extends Activity {
|
|||||||
|
|
||||||
workout= selectedWorkout;
|
workout= selectedWorkout;
|
||||||
samples= Arrays.asList(Instance.getInstance(this).db.workoutDao().getAllSamplesOfWorkout(workout.id));
|
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);
|
setContentView(R.layout.activity_show_workout);
|
||||||
|
|
||||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
@ -77,28 +92,31 @@ public class ShowWorkoutActivity extends Activity {
|
|||||||
|
|
||||||
root= findViewById(R.id.showWorkoutRoot);
|
root= findViewById(R.id.showWorkoutRoot);
|
||||||
|
|
||||||
addTitle("Zeit");
|
addTitle(getString(R.string.workoutTime));
|
||||||
addKeyValue("Datum", getDate(), "Dauer", UnitUtils.getHourMinuteTime(workout.getDuration()));
|
addKeyValue(getString(R.string.workoutDate), getDate());
|
||||||
addKeyValue("Startzeit", SimpleDateFormat.getTimeInstance().format(new Date(workout.start)),
|
addKeyValue(getString(R.string.workoutDuration), UnitUtils.getHourMinuteSecondTime(workout.duration),
|
||||||
"Endzeit", SimpleDateFormat.getTimeInstance().format(new Date(workout.end)));
|
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");
|
addTitle(getString(R.string.workoutRoute));
|
||||||
|
|
||||||
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();
|
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(){
|
String getDate(){
|
||||||
@ -113,6 +131,7 @@ public class ShowWorkoutActivity extends Activity {
|
|||||||
textView.setTextColor(getThemePrimaryColor());
|
textView.setTextColor(getThemePrimaryColor());
|
||||||
textView.setTypeface(Typeface.DEFAULT_BOLD);
|
textView.setTypeface(Typeface.DEFAULT_BOLD);
|
||||||
textView.setAllCaps(true);
|
textView.setAllCaps(true);
|
||||||
|
textView.setPadding(0, 20, 0, 0);
|
||||||
|
|
||||||
root.addView(textView);
|
root.addView(textView);
|
||||||
}
|
}
|
||||||
@ -137,8 +156,62 @@ public class ShowWorkoutActivity extends Activity {
|
|||||||
root.addView(v);
|
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(){
|
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));
|
root.addView(map, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getWindowManager().getDefaultDisplay().getWidth()*3/4));
|
||||||
map.setAlpha(0);
|
map.setAlpha(0);
|
||||||
}
|
|
||||||
|
|
||||||
private int getThemePrimaryColor() {
|
|
||||||
final TypedValue value = new TypedValue ();
|
Paint pGreen= AndroidGraphicFactory.INSTANCE.createPaint();
|
||||||
getTheme().resolveAttribute (android.R.attr.colorPrimary, value, true);
|
pGreen.setColor(Color.GREEN);
|
||||||
return value.data;
|
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
|
@Override
|
||||||
@ -198,11 +273,29 @@ public class ShowWorkoutActivity extends Activity {
|
|||||||
return true;
|
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
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
if(id == R.id.actionDeleteWorkout){
|
if(id == R.id.actionDeleteWorkout){
|
||||||
// TODO: delete workout
|
showDeleteDialog();
|
||||||
return true;
|
return true;
|
||||||
}else if(id == android.R.id.home){
|
}else if(id == android.R.id.home){
|
||||||
finish();
|
finish();
|
||||||
@ -211,18 +304,4 @@ public class ShowWorkoutActivity extends Activity {
|
|||||||
return super.onOptionsItemSelected(item);
|
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.Database;
|
||||||
import androidx.room.RoomDatabase;
|
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 class AppDatabase extends RoomDatabase {
|
||||||
public abstract WorkoutDao workoutDao();
|
public abstract WorkoutDao workoutDao();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,10 @@ public class Workout{
|
|||||||
public long start;
|
public long start;
|
||||||
public long end;
|
public long end;
|
||||||
|
|
||||||
|
public long duration;
|
||||||
|
|
||||||
|
public long pauseDuration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Length of workout in meters
|
* Length of workout in meters
|
||||||
*/
|
*/
|
||||||
@ -60,9 +64,5 @@ public class Workout{
|
|||||||
|
|
||||||
public int calorie;
|
public int calorie;
|
||||||
|
|
||||||
public long getDuration(){
|
|
||||||
return end - start;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -20,8 +20,10 @@
|
|||||||
package de.tadris.fitness.data;
|
package de.tadris.fitness.data;
|
||||||
|
|
||||||
import androidx.room.Dao;
|
import androidx.room.Dao;
|
||||||
|
import androidx.room.Delete;
|
||||||
import androidx.room.Insert;
|
import androidx.room.Insert;
|
||||||
import androidx.room.Query;
|
import androidx.room.Query;
|
||||||
|
import androidx.room.Update;
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public interface WorkoutDao {
|
public interface WorkoutDao {
|
||||||
@ -29,7 +31,7 @@ public interface WorkoutDao {
|
|||||||
@Query("SELECT * FROM workout_sample WHERE workout_id = :workout_id")
|
@Query("SELECT * FROM workout_sample WHERE workout_id = :workout_id")
|
||||||
WorkoutSample[] getAllSamplesOfWorkout(long workout_id);
|
WorkoutSample[] getAllSamplesOfWorkout(long workout_id);
|
||||||
|
|
||||||
@Query("SELECT * FROM workout")
|
@Query("SELECT * FROM workout ORDER BY start DESC")
|
||||||
Workout[] getWorkouts();
|
Workout[] getWorkouts();
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
@ -38,7 +40,11 @@ public interface WorkoutDao {
|
|||||||
@Insert
|
@Insert
|
||||||
void insertWorkout(Workout workout);
|
void insertWorkout(Workout workout);
|
||||||
|
|
||||||
@Query("SELECT * FROM workout ORDER BY start DESC LIMIT 1")
|
@Delete
|
||||||
Workout findLastWorkout();
|
void deleteWorkout(Workout workout);
|
||||||
|
|
||||||
|
@Update
|
||||||
|
void updateWorkout(Workout workout);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,13 +37,15 @@ public class WorkoutManager {
|
|||||||
// Calculating values
|
// Calculating values
|
||||||
double length= 0;
|
double length= 0;
|
||||||
for(int i= 1; i < samples.size(); i++){
|
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.length= (int)length;
|
||||||
workout.avgSpeed= ((double) workout.length) / ((double) workout.getDuration() / 1000);
|
workout.avgSpeed= ((double) workout.length) / ((double) workout.duration / 1000);
|
||||||
workout.avgPace= (double)(workout.getDuration() / 1000 / 60) / ((double) workout.length / 1000);
|
workout.avgPace= ((double)workout.duration / 1000 / 60) / ((double) workout.length / 1000);
|
||||||
workout.calorie= CalorieCalculator.calculateCalories(workout, 80);
|
workout.calorie= CalorieCalculator.calculateCalories(workout, Instance.getInstance(context).userPreferences.weight);
|
||||||
// TODO: use user weight
|
|
||||||
|
|
||||||
// Setting workoutId in the samples
|
// Setting workoutId in the samples
|
||||||
int i= 0;
|
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.ColumnInfo;
|
||||||
import androidx.room.Entity;
|
import androidx.room.Entity;
|
||||||
import androidx.room.ForeignKey;
|
import androidx.room.ForeignKey;
|
||||||
|
import androidx.room.Ignore;
|
||||||
import androidx.room.PrimaryKey;
|
import androidx.room.PrimaryKey;
|
||||||
|
|
||||||
|
import com.github.mikephil.charting.data.Entry;
|
||||||
|
|
||||||
import org.mapsforge.core.model.LatLong;
|
import org.mapsforge.core.model.LatLong;
|
||||||
|
|
||||||
import static androidx.room.ForeignKey.CASCADE;
|
import static androidx.room.ForeignKey.CASCADE;
|
||||||
@ -42,14 +45,26 @@ public class WorkoutSample{
|
|||||||
@ColumnInfo(name = "workout_id")
|
@ColumnInfo(name = "workout_id")
|
||||||
public long workoutId;
|
public long workoutId;
|
||||||
|
|
||||||
public long time;
|
public long absoluteTime;
|
||||||
|
|
||||||
|
public long relativeTime;
|
||||||
|
|
||||||
public double lat;
|
public double lat;
|
||||||
|
|
||||||
public double lon;
|
public double lon;
|
||||||
|
|
||||||
|
public double elevation;
|
||||||
|
|
||||||
|
public double relativeElevation;
|
||||||
|
|
||||||
public double speed;
|
public double speed;
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public Entry tmpEntry;
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public double tmpRoundedSpeed;
|
||||||
|
|
||||||
public LatLong toLatLong(){
|
public LatLong toLatLong(){
|
||||||
return new LatLong(lat, lon);
|
return new LatLong(lat, lon);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -124,7 +124,7 @@ public class LocationListener implements android.location.LocationListener {
|
|||||||
|
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
for (String provider : this.locationManager.getProviders(true)) {
|
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;
|
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) {
|
if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -32,17 +32,33 @@ import de.tadris.fitness.Instance;
|
|||||||
import de.tadris.fitness.data.Workout;
|
import de.tadris.fitness.data.Workout;
|
||||||
import de.tadris.fitness.data.WorkoutManager;
|
import de.tadris.fitness.data.WorkoutManager;
|
||||||
import de.tadris.fitness.data.WorkoutSample;
|
import de.tadris.fitness.data.WorkoutSample;
|
||||||
|
import de.tadris.fitness.util.CalorieCalculator;
|
||||||
|
|
||||||
public class WorkoutRecorder implements LocationListener.LocationChangeListener {
|
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 Context context;
|
||||||
private Workout workout;
|
private Workout workout;
|
||||||
private RecordingState state;
|
private RecordingState state;
|
||||||
private List<WorkoutSample> samples= new ArrayList<>();
|
private final List<WorkoutSample> samples= new ArrayList<>();
|
||||||
private long time= 0;
|
private long time= 0;
|
||||||
|
private long pauseTime= 0;
|
||||||
private long lastResume;
|
private long lastResume;
|
||||||
|
private long lastPause= 0;
|
||||||
|
private long lastSampleTime= 0;
|
||||||
|
private double distance= 0;
|
||||||
|
|
||||||
public WorkoutRecorder(Context context, String workoutType) {
|
public WorkoutRecorder(Context context, String workoutType) {
|
||||||
this.context= context;
|
this.context= context;
|
||||||
@ -54,9 +70,11 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
|
|||||||
|
|
||||||
public void start(){
|
public void start(){
|
||||||
if(state == RecordingState.IDLE){
|
if(state == RecordingState.IDLE){
|
||||||
Log.i("Recorder", "");
|
Log.i("Recorder", "Start");
|
||||||
workout.start= System.currentTimeMillis();
|
workout.start= System.currentTimeMillis();
|
||||||
resume();
|
resume();
|
||||||
|
Instance.getInstance(context).locationListener.registerLocationChangeListeners(this);
|
||||||
|
startWatchdog();
|
||||||
}else if(state == RecordingState.PAUSED){
|
}else if(state == RecordingState.PAUSED){
|
||||||
resume();
|
resume();
|
||||||
}else if(state != RecordingState.RUNNING){
|
}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(){
|
private void resume(){
|
||||||
|
Log.i("Recorder", "Resume");
|
||||||
state= RecordingState.RUNNING;
|
state= RecordingState.RUNNING;
|
||||||
lastResume= System.currentTimeMillis();
|
lastResume= System.currentTimeMillis();
|
||||||
Instance.getInstance(context).locationListener.registerLocationChangeListeners(this);
|
if(lastPause != 0){
|
||||||
|
pauseTime+= System.currentTimeMillis() - lastPause;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pause(){
|
public void pause(){
|
||||||
if(state == RecordingState.RUNNING){
|
if(state == RecordingState.RUNNING){
|
||||||
|
Log.i("Recorder", "Pause");
|
||||||
state= RecordingState.PAUSED;
|
state= RecordingState.PAUSED;
|
||||||
time+= System.currentTimeMillis() - lastResume;
|
time+= System.currentTimeMillis() - lastResume;
|
||||||
Instance.getInstance(context).locationListener.unregisterLocationChangeListeners(this);
|
lastPause= System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop(){
|
public void stop(){
|
||||||
|
Log.i("Recorder", "Stop");
|
||||||
|
if(state == RecordingState.PAUSED){
|
||||||
|
resume();
|
||||||
|
}
|
||||||
pause();
|
pause();
|
||||||
workout.end= System.currentTimeMillis();
|
workout.end= System.currentTimeMillis();
|
||||||
|
workout.duration= time;
|
||||||
|
workout.pauseDuration= pauseTime;
|
||||||
state= RecordingState.STOPPED;
|
state= RecordingState.STOPPED;
|
||||||
|
Instance.getInstance(context).locationListener.unregisterLocationChangeListeners(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save(){
|
public void save(){
|
||||||
if(state != RecordingState.STOPPED){
|
if(state != RecordingState.STOPPED){
|
||||||
throw new IllegalStateException("Cannot save recording, recorder was not stopped. state = " + state);
|
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(){
|
public int getSampleCount(){
|
||||||
return samples.size();
|
synchronized (samples){
|
||||||
|
return samples.size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLocationChange(Location location) {
|
public void onLocationChange(Location location) {
|
||||||
if(state == RecordingState.RUNNING){
|
if(isActive()){
|
||||||
|
double distance= 0;
|
||||||
if(getSampleCount() > 0){
|
if(getSampleCount() > 0){
|
||||||
WorkoutSample lastSample= samples.get(samples.size() - 1);
|
synchronized (samples){
|
||||||
if(LocationListener.locationToLatLong(location).sphericalDistance(new LatLong(lastSample.lat, lastSample.lon)) < MIN_DISTANCE){
|
WorkoutSample lastSample= samples.get(samples.size() - 1);
|
||||||
return;
|
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();
|
lastSampleTime= System.currentTimeMillis();
|
||||||
sample.lat= location.getLatitude();
|
if(state == RecordingState.RUNNING && location.getTime() > workout.start){
|
||||||
sample.lon= location.getLongitude();
|
if(samples.size() == 2){
|
||||||
sample.speed= location.getSpeed();
|
lastResume= System.currentTimeMillis();
|
||||||
sample.time= location.getTime();
|
workout.start= System.currentTimeMillis();
|
||||||
samples.add(sample);
|
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
|
* @return calories burned
|
||||||
*/
|
*/
|
||||||
public static int calculateCalories(Workout workout, double weight){
|
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);
|
return (int)(mins * (getMET(workout) * 3.5 * weight) / 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* calorie calculation based on @link { https://www.topendsports.com/weight-loss/energy-met.htm }
|
* 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
|
* @param workout
|
||||||
* @return MET
|
* @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;
|
package de.tadris.fitness.util;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import de.tadris.fitness.R;
|
import de.tadris.fitness.R;
|
||||||
import de.tadris.fitness.data.Workout;
|
import de.tadris.fitness.data.Workout;
|
||||||
|
|
||||||
public class ThemeManager {
|
public class ThemeManager {
|
||||||
|
|
||||||
|
public static int getThemeByWorkoutType(String type){
|
||||||
public static int getThemeByWorkout(Workout workout, Context context){
|
switch (type){
|
||||||
switch (workout.workoutType){
|
|
||||||
case Workout.WORKOUT_TYPE_RUNNING: return R.style.Running;
|
case Workout.WORKOUT_TYPE_RUNNING: return R.style.Running;
|
||||||
case Workout.WORKOUT_TYPE_CYCLING: return R.style.Bicycling;
|
case Workout.WORKOUT_TYPE_CYCLING: return R.style.Bicycling;
|
||||||
default: return R.style.AppTheme;
|
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
|
* @param distance Distance in meters
|
||||||
* @return String in preferred unit
|
* @return String in preferred unit
|
||||||
*/
|
*/
|
||||||
public static String getDistance(int distance){
|
public static String getDistance(int distance){
|
||||||
// TODO: use preferred unit by user
|
// TODO: use preferred unit chosen by user
|
||||||
if(distance >= 1000){
|
if(distance >= 1000){
|
||||||
return getDistanceInKilometers((double)distance);
|
return getDistanceInKilometers((double)distance);
|
||||||
}else{
|
}else{
|
||||||
@ -54,7 +85,7 @@ public class UnitUtils {
|
|||||||
* @return speed in km/h
|
* @return speed in km/h
|
||||||
*/
|
*/
|
||||||
public static String getSpeed(double speed){
|
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";
|
return round(speed*3.6, 1) + " km/h";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,8 +18,17 @@
|
|||||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ 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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".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"
|
<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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@ -30,13 +31,6 @@
|
|||||||
android:layout_above="@id/recordInfoRoot">
|
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
|
<LinearLayout
|
||||||
android:id="@+id/recordMapViewrRoot"
|
android:id="@+id/recordMapViewrRoot"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -46,6 +40,14 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/location_marker" />
|
||||||
|
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@ -58,11 +60,132 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView"
|
android:id="@+id/recordTime"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="20sp"
|
android:fontFamily="sans-serif-black"
|
||||||
android:text="TextView" />
|
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>
|
</LinearLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
@ -98,6 +98,6 @@
|
|||||||
android:layout_marginEnd="207dp"
|
android:layout_marginEnd="207dp"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintGuide_begin="204dp"
|
app:layout_constraintGuide_percent="0.5"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
</android.support.constraint.ConstraintLayout>
|
</android.support.constraint.ConstraintLayout>
|
||||||
@ -23,5 +23,5 @@
|
|||||||
<item
|
<item
|
||||||
android:id="@+id/actionDeleteWorkout"
|
android:id="@+id/actionDeleteWorkout"
|
||||||
android:showAsAction="ifRoom"
|
android:showAsAction="ifRoom"
|
||||||
android:title="@string/deleteWorkout" />
|
android:title="@string/delete" />
|
||||||
</menu>
|
</menu>
|
||||||
@ -21,5 +21,26 @@
|
|||||||
<string name="app_name">FitoTrack</string>
|
<string name="app_name">FitoTrack</string>
|
||||||
<string name="workout_add">Add</string>
|
<string name="workout_add">Add</string>
|
||||||
<string name="workoutStopRecording">Stop</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>
|
</resources>
|
||||||
|
|||||||
@ -19,6 +19,14 @@
|
|||||||
|
|
||||||
<resources>
|
<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. -->
|
<!-- Base application theme. -->
|
||||||
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
|
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
|
||||||
<!-- Customize your theme here. -->
|
<!-- Customize your theme here. -->
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user