Record workout, List workouts

This commit is contained in:
jannis 2019-08-17 16:52:15 +02:00
parent cdcf2ddce2
commit 4aa8a5c6aa
23 changed files with 1057 additions and 21 deletions

View File

@ -1,5 +1,32 @@
/*
* 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/>.
*/
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
allprojects {
dependencies {
repositories {
jcenter()
}
}
}
android { android {
compileSdkVersion 28 compileSdkVersion 28
buildToolsVersion "29.0.1" buildToolsVersion "29.0.1"
@ -20,9 +47,19 @@ android {
} }
dependencies { dependencies {
def room_version = "2.2.0-alpha02"
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation "androidx.room:room-runtime:2.2.0-alpha02" implementation "androidx.room:room-runtime:$room_version"
implementation 'org.mapsforge:mapsforge-core:0.11.0'
implementation 'org.mapsforge:mapsforge-map:0.11.0'
implementation 'org.mapsforge:mapsforge-map-reader:0.11.0'
implementation 'org.mapsforge:mapsforge-themes:0.11.0'
implementation 'org.mapsforge:mapsforge-map-android:0.11.0'
implementation 'com.caverock:androidsvg:1.3'
implementation 'net.sf.kxml:kxml2:2.3.0'
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'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

View File

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

View File

@ -24,6 +24,7 @@ import android.content.Context;
import androidx.room.Room; import androidx.room.Room;
import de.tadris.fitness.data.AppDatabase; import de.tadris.fitness.data.AppDatabase;
import de.tadris.fitness.location.LocationListener;
public class Instance { public class Instance {
@ -39,8 +40,12 @@ public class Instance {
} }
public AppDatabase db; public AppDatabase db;
public LocationListener locationListener;
private Instance(Context context) { private Instance(Context context) {
db = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME).allowMainThreadQueries().build(); db = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
.allowMainThreadQueries()
.build();
locationListener= new LocationListener(context);
} }
} }

View File

@ -20,21 +20,20 @@
package de.tadris.fitness; package de.tadris.fitness;
import android.app.Activity; import android.app.Activity;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.ViewGroup; import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import de.tadris.fitness.data.Workout; import de.tadris.fitness.data.Workout;
public class ListWorkoutsActivity extends Activity implements WorkoutAdapter.WorkoutAdapterListener { public class ListWorkoutsActivity extends Activity implements WorkoutAdapter.WorkoutAdapterListener {
private RecyclerView listView; private RecyclerView listView;
private RecyclerView.Adapter mAdapter; private RecyclerView.Adapter adapter;
private RecyclerView.LayoutManager layoutManager; private RecyclerView.LayoutManager layoutManager;
Workout[] workouts; Workout[] workouts;
@ -50,12 +49,38 @@ public class ListWorkoutsActivity extends Activity implements WorkoutAdapter.Wor
layoutManager= new LinearLayoutManager(this); layoutManager= new LinearLayoutManager(this);
listView.setLayoutManager(layoutManager); listView.setLayoutManager(layoutManager);
}
@Override
public void onResume() {
super.onResume();
workouts= Instance.getInstance(this).db.workoutDao().getWorkouts(); workouts= Instance.getInstance(this).db.workoutDao().getWorkouts();
listView.setAdapter(new WorkoutAdapter(workouts, this)); adapter= new WorkoutAdapter(workouts, this);
listView.setAdapter(adapter);
} }
@Override @Override
public void onItemClick(Workout workout) { public void onItemClick(Workout workout) {
// TODO: open detail View // TODO: open detail View
} }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.list_workout_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == R.id.action_workout_add){
startActivity(new Intent(this, RecordWorkoutActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
} }

View File

@ -1,19 +1,158 @@
/*
* 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; package de.tadris.fitness;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle; import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
public class RecordWorkoutActivity extends Activity { import androidx.core.app.ActivityCompat;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.util.AndroidUtil;
import org.mapsforge.map.android.view.MapView;
import org.mapsforge.map.layer.cache.TileCache;
import org.mapsforge.map.layer.download.TileDownloadLayer;
import de.tadris.fitness.data.Workout;
import de.tadris.fitness.location.LocationListener;
import de.tadris.fitness.location.MyLocationOverlay;
import de.tadris.fitness.location.WorkoutRecorder;
import de.tadris.fitness.map.HumanitarianTileSource;
public class RecordWorkoutActivity extends Activity implements LocationListener.LocationChangeListener {
MapView mapView;
MyLocationOverlay locationOverlay;
TileDownloadLayer downloadLayer;
WorkoutRecorder recorder;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_record_workout); setContentView(R.layout.activity_record_workout);
this.mapView= new MapView(this);
mapView.setZoomLevelMin((byte) 18);
mapView.setZoomLevelMax((byte) 18);
mapView.setBuiltInZoomControls(false);
TileCache tileCache = AndroidUtil.createTileCache(this, "mapcache", mapView.getModel().displayModel.getTileSize(), 1f, this.mapView.getModel().frameBufferModel.getOverdrawFactor(), true);
HumanitarianTileSource tileSource = HumanitarianTileSource.INSTANCE;
tileSource.setUserAgent("mapsforge-android");
downloadLayer = new TileDownloadLayer(tileCache, mapView.getModel().mapViewPosition, tileSource, AndroidGraphicFactory.INSTANCE);
mapView.getLayerManager().getLayers().add(downloadLayer);
locationOverlay= new MyLocationOverlay(Instance.getInstance(this).locationListener, getDrawable(R.drawable.location_marker));
mapView.getLayerManager().redrawLayers();
mapView.setZoomLevel((byte) 18);
mapView.setCenter(new LatLong(52, 13));
((ViewGroup)findViewById(R.id.recordMapViewrRoot)).addView(mapView);
checkPermissions();
recorder= new WorkoutRecorder(this, Workout.WORKOUT_TYPE_RUNNING);
recorder.start();
}
private void stopAndSave(){
recorder.stop();
if(recorder.getSampleCount() > 3){
recorder.save();
}
finish();
}
void checkPermissions(){
if (!hasPermission()) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 10);
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 10);
}
}
public boolean hasPermission(){
return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (hasPermission()) {
Instance.getInstance(this).locationListener.enableMyLocation();
}
}
@Override
public void onLocationChange(Location location) {
mapView.getModel().mapViewPosition.animateTo(LocationListener.locationToLatLong(location));
locationOverlay.setPosition(location.getLatitude(), location.getLongitude(), location.getAccuracy());
}
@Override
protected void onDestroy() {
recorder.stop();
mapView.destroyAll();
AndroidGraphicFactory.clearResourceMemoryCache();
super.onDestroy();
}
@Override
public void onPause(){
super.onPause();
downloadLayer.onPause();
Instance.getInstance(this).locationListener.unregisterLocationChangeListeners(this);
}
public void onResume(){
super.onResume();
downloadLayer.onResume();
Instance.getInstance(this).locationListener.registerLocationChangeListeners(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.record_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == R.id.actionRecordingStop){
stopAndSave();
return true;
}
return super.onOptionsItemSelected(item);
} }
void onMenuCreate(){
}
} }

View File

@ -1,3 +1,22 @@
/*
* 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; package de.tadris.fitness;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -45,7 +64,7 @@ public class WorkoutAdapter extends RecyclerView.Adapter<WorkoutAdapter.WorkoutV
// Replace the contents of a view (invoked by the layout manager) // Replace the contents of a view (invoked by the layout manager)
@Override @Override
public void onBindViewHolder(WorkoutViewHolder holder, final int position) { public void onBindViewHolder(WorkoutViewHolder holder, final int position) {
holder.lengthText.setText(workouts[position].length + "km"); holder.lengthText.setText(UnitUtils.getDistance(workouts[position].length));
holder.timeText.setText(UnitUtils.getHourMinuteTime(workouts[position].getTime())); holder.timeText.setText(UnitUtils.getHourMinuteTime(workouts[position].getTime()));
holder.root.setOnClickListener(new View.OnClickListener() { holder.root.setOnClickListener(new View.OnClickListener() {
@Override @Override

View File

@ -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(entities = {Workout.class, WorkoutSample.class}, version = 1) @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();
} }

View File

@ -33,11 +33,28 @@ public class Workout{
public long start; public long start;
public long end; public long end;
public double length;
/**
* Length of workout in meters
*/
public int length;
/**
* Average speed of workout in m/s
*/
public double avgSpeed; public double avgSpeed;
/**
* Average pace of workout in km/min
*/
public double avgPace; public double avgPace;
public String workoutType; public String workoutType;
/**
* Gets time of workout
* @return time in milliseconds
*/
public long getTime(){ public long getTime(){
return end - start; return end - start;
} }

View File

@ -20,6 +20,7 @@
package de.tadris.fitness.data; package de.tadris.fitness.data;
import androidx.room.Dao; import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query; import androidx.room.Query;
@Dao @Dao
@ -31,4 +32,13 @@ public interface WorkoutDao {
@Query("SELECT * FROM workout") @Query("SELECT * FROM workout")
Workout[] getWorkouts(); Workout[] getWorkouts();
@Insert
void insertWorkoutAndSamples(Workout workout, WorkoutSample[] samples);
@Insert
void insertWorkout(Workout workout);
@Query("SELECT * FROM workout ORDER BY start DESC LIMIT 1")
Workout findLastWorkout();
} }

View File

@ -0,0 +1,53 @@
/*
* 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.data;
import java.util.List;
import de.tadris.fitness.Instance;
public class WorkoutManager {
public static void insertWorkout(Instance instance, Workout workout, List<WorkoutSample> samples){
workout.id= System.currentTimeMillis();
// Calculating values
double length= 0;
for(int i= 1; i < samples.size(); i++){
length+= samples.get(i - 1).toLatLong().sphericalDistance(samples.get(i).toLatLong());
}
workout.length= (int)length;
workout.avgSpeed= ((double) workout.length / 1000) / ((double) workout.getTime() / 1000);
workout.avgPace= (double)(workout.getTime() / 1000 / 60) / ((double) workout.length / 1000);
// Setting workoutId in the samples
int i= 0;
for(WorkoutSample sample : samples){
i++;
sample.id= workout.id + i;
sample.workoutId= workout.id;
}
// Saving workout and samples
instance.db.workoutDao().insertWorkoutAndSamples(workout, samples.toArray(new WorkoutSample[0]));
}
}

View File

@ -24,6 +24,8 @@ import androidx.room.Entity;
import androidx.room.ForeignKey; import androidx.room.ForeignKey;
import androidx.room.PrimaryKey; import androidx.room.PrimaryKey;
import org.mapsforge.core.model.LatLong;
import static androidx.room.ForeignKey.CASCADE; import static androidx.room.ForeignKey.CASCADE;
@Entity(tableName = "workout_sample", @Entity(tableName = "workout_sample",
@ -48,5 +50,9 @@ public class WorkoutSample{
public double speed; public double speed;
public LatLong toLatLong(){
return new LatLong(lat, lon);
}
} }

View File

@ -0,0 +1,162 @@
/*
* 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.location;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import androidx.core.app.ActivityCompat;
import org.mapsforge.core.model.LatLong;
import java.util.ArrayList;
import java.util.List;
public class LocationListener implements android.location.LocationListener {
public static LatLong static_lastLocation;
/**
* @param location the location whose geographical coordinates should be converted.
* @return a new LatLong with the geographical coordinates taken from the given location.
*/
public static LatLong locationToLatLong(Location location) {
return new LatLong(location.getLatitude(), location.getLongitude());
}
private Context activity;
private Location lastLocation;
private final LocationManager locationManager;
private boolean myLocationEnabled;
public LocationListener(Context context) {
super();
this.activity= context;
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
}
/**
* Stops the receiving of location updates. Has no effect if location updates are already disabled.
*/
public synchronized void disableMyLocation() {
if (this.myLocationEnabled) {
this.myLocationEnabled = false;
try {
this.locationManager.removeUpdates(this);
} catch (RuntimeException runtimeException) {
// do we need to catch security exceptions for this call on Android 6?
}
}
}
public synchronized void enableMyLocation() {
enableBestAvailableProvider();
}
/**
* @return the most-recently received location fix (might be null).
*/
public synchronized Location getLastLocation() {
return this.lastLocation;
}
/**
* @return true if the receiving of location updates is currently enabled, false otherwise.
*/
public synchronized boolean isMyLocationEnabled() {
return this.myLocationEnabled;
}
@Override
public void onLocationChanged(Location location) {
synchronized (this) {
this.lastLocation = location;
LatLong latLong = locationToLatLong(location);
static_lastLocation= latLong;
for(LocationChangeListener listener : this.locationChangeListeners){
listener.onLocationChange(location);
}
}
}
@Override
public void onProviderDisabled(String provider) {
enableBestAvailableProvider();
}
@Override
public void onProviderEnabled(String provider) {
enableBestAvailableProvider();
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// do nothing
}
private void enableBestAvailableProvider() {
disableMyLocation();
boolean result = false;
for (String provider : this.locationManager.getProviders(true)) {
if (LocationManager.GPS_PROVIDER.equals(provider) || LocationManager.NETWORK_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;
}
this.locationManager.requestLocationUpdates(provider, 0, 0, this);
Location location= this.locationManager.getLastKnownLocation(provider);
if(location != null){
onLocationChanged(location);
}
}
}
this.myLocationEnabled = result;
}
private List<LocationChangeListener> locationChangeListeners= new ArrayList<>();
public void registerLocationChangeListeners(LocationChangeListener listener){
if(locationChangeListeners.size() == 0){
enableMyLocation();
}
locationChangeListeners.add(listener);
}
public void unregisterLocationChangeListeners(LocationChangeListener listener){
locationChangeListeners.remove(listener);
if(locationChangeListeners.size() == 0){
disableMyLocation();
}
}
public interface LocationChangeListener{
void onLocationChange(Location location);
}
}

View File

@ -0,0 +1,98 @@
/*
* 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.location;
import android.graphics.drawable.Drawable;
import org.mapsforge.core.graphics.Canvas;
import org.mapsforge.core.graphics.Paint;
import org.mapsforge.core.graphics.Style;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.core.model.Point;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.layer.Layer;
import org.mapsforge.map.layer.overlay.Circle;
import org.mapsforge.map.layer.overlay.Marker;
public class MyLocationOverlay extends Layer {
private final Circle circle;
private final Marker marker;
private final LocationListener locationListener;
private static Paint getDefaultFixedPixelCircleFill() {
return getPaint(AndroidGraphicFactory.INSTANCE.createColor(255, 0, 0, 255), 0, Style.FILL);
}
private static Paint getDefaultOuterFixedPixelCircleFill(){
return getPaint(AndroidGraphicFactory.INSTANCE.createColor(30, 30, 30, 255), 0, Style.FILL);
}
private static Paint getDefaultFixedPixelCircleStroke() {
return getPaint(AndroidGraphicFactory.INSTANCE.createColor(255, 255, 255, 255), 7, Style.STROKE);
}
private static Paint getPaint(int color, int strokeWidth, Style style) {
Paint paint = AndroidGraphicFactory.INSTANCE.createPaint();
paint.setColor(color);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(style);
return paint;
}
public MyLocationOverlay(LocationListener locationListener, Drawable icon) {
this.locationListener= locationListener;
this.circle= new Circle(null, 0f, getDefaultFixedPixelCircleFill(), null);
this.marker= new Marker(null, AndroidGraphicFactory.convertToBitmap(icon), 26, 26);
}
@Override
public synchronized void draw(BoundingBox boundingBox, byte zoomLevel, Canvas canvas, Point topLeftPoint) {
if (this.circle != null) {
this.circle.draw(boundingBox, zoomLevel, canvas, topLeftPoint);
}
this.marker.draw(boundingBox, zoomLevel, canvas, topLeftPoint);
}
@Override
protected void onAdd() {
this.circle.setDisplayModel(this.displayModel);
this.marker.setDisplayModel(this.displayModel);
}
@Override
public void onDestroy() {
this.marker.onDestroy();
}
public void setPosition(double latitude, double longitude, float accuracy) {
synchronized (this) {
LatLong latLong = new LatLong(latitude, longitude);
this.marker.setLatLong(latLong);
if (this.circle != null) {
this.circle.setLatLong(latLong);
this.circle.setRadius(accuracy);
}
requestRedraw();
}
}
}

View File

@ -0,0 +1,121 @@
/*
* 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.location;
import android.content.Context;
import android.location.Location;
import android.util.Log;
import org.mapsforge.core.model.LatLong;
import java.util.ArrayList;
import java.util.List;
import de.tadris.fitness.Instance;
import de.tadris.fitness.data.Workout;
import de.tadris.fitness.data.WorkoutManager;
import de.tadris.fitness.data.WorkoutSample;
public class WorkoutRecorder implements LocationListener.LocationChangeListener {
private static final int MIN_DISTANCE= 5;
private Context context;
private Workout workout;
private RecordingState state;
private List<WorkoutSample> samples= new ArrayList<>();
private long time= 0;
private long lastResume;
public WorkoutRecorder(Context context, String workoutType) {
this.context= context;
this.state= RecordingState.IDLE;
this.workout= new Workout();
this.workout.workoutType= workoutType;
}
public void start(){
if(state == RecordingState.IDLE){
Log.i("Recorder", "");
workout.start= System.currentTimeMillis();
resume();
}else if(state == RecordingState.PAUSED){
resume();
}else if(state != RecordingState.RUNNING){
throw new IllegalStateException("Cannot start or resume recording. state = " + state);
}
}
private void resume(){
state= RecordingState.RUNNING;
lastResume= System.currentTimeMillis();
Instance.getInstance(context).locationListener.registerLocationChangeListeners(this);
}
public void pause(){
if(state == RecordingState.RUNNING){
state= RecordingState.PAUSED;
time+= System.currentTimeMillis() - lastResume;
Instance.getInstance(context).locationListener.unregisterLocationChangeListeners(this);
}
}
public void stop(){
pause();
workout.end= System.currentTimeMillis();
state= RecordingState.STOPPED;
}
public void save(){
if(state != RecordingState.STOPPED){
throw new IllegalStateException("Cannot save recording, recorder was not stopped. state = " + state);
}
WorkoutManager.insertWorkout(Instance.getInstance(context), workout, samples);
}
public int getSampleCount(){
return samples.size();
}
@Override
public void onLocationChange(Location location) {
if(state == RecordingState.RUNNING){
if(getSampleCount() > 0){
WorkoutSample lastSample= samples.get(samples.size() - 1);
if(LocationListener.locationToLatLong(location).sphericalDistance(new LatLong(lastSample.lat, lastSample.lon)) < MIN_DISTANCE){
return;
}
}
WorkoutSample sample= new WorkoutSample();
sample.lat= location.getLatitude();
sample.lon= location.getLongitude();
sample.speed= location.getSpeed();
sample.time= location.getTime();
samples.add(sample);
}
}
enum RecordingState{
IDLE, RUNNING, PAUSED, STOPPED
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
*
* This file is part of FitoTrack
*
* FitoTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FitoTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.tadris.fitness.map;
import org.mapsforge.core.model.Tile;
import org.mapsforge.map.layer.download.tilesource.AbstractTileSource;
import java.net.MalformedURLException;
import java.net.URL;
public class HumanitarianTileSource extends AbstractTileSource {
public static HumanitarianTileSource INSTANCE= new HumanitarianTileSource(new String[]{"tile-a.openstreetmap.fr", "tile-b.openstreetmap.fr", "tile-c.openstreetmap.fr"}, 443);
private static final int PARALLEL_REQUESTS_LIMIT = 8;
private static final String PROTOCOL = "https";
private static final int ZOOM_LEVEL_MAX = 18;
private static final int ZOOM_LEVEL_MIN = 0;
public HumanitarianTileSource(String[] hostNames, int port) {
super(hostNames, port);
defaultTimeToLive = 864000000; // Ten days
}
@Override
public int getParallelRequestsLimit() {
return PARALLEL_REQUESTS_LIMIT;
}
@Override
public URL getTileUrl(Tile tile) throws MalformedURLException {
return new URL(PROTOCOL, getHostName(), this.port, "/hot/" + tile.zoomLevel + '/' + tile.tileX + '/' + tile.tileY + ".png");
}
@Override
public byte getZoomLevelMax() {
return ZOOM_LEVEL_MAX;
}
@Override
public byte getZoomLevelMin() {
return ZOOM_LEVEL_MIN;
}
@Override
public boolean hasAlpha() {
return false;
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
*
* This file is part of FitoTrack
*
* FitoTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FitoTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.tadris.fitness.map;
import org.mapsforge.map.rendertheme.XmlRenderTheme;
import org.mapsforge.map.rendertheme.XmlRenderThemeMenuCallback;
import java.io.InputStream;
/**
* Enumeration of all internal rendering themes.
*/
public enum InternalRenderTheme implements XmlRenderTheme {
OLD("/assets/rendertheme/default.xml"),
DEFAULT("/assets/rendertheme/osmarender.xml");
private final String path;
private XmlRenderThemeMenuCallback callback;
InternalRenderTheme(String path) {
this.path = path;
}
@Override
public XmlRenderThemeMenuCallback getMenuCallback() {
return callback;
}
/**
* @return the prefix for all relative resource paths.
*/
@Override
public String getRelativePathPrefix() {
return "/assets/";
}
@Override
public InputStream getRenderThemeAsStream() {
return getClass().getResourceAsStream(this.path);
}
@Override
public void setMenuCallback(XmlRenderThemeMenuCallback menuCallback) {
callback= menuCallback;
}
}

View File

@ -1,6 +1,23 @@
package de.tadris.fitness.util; /*
* 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/>.
*/
import android.content.Context; package de.tadris.fitness.util;
public class UnitUtils { public class UnitUtils {
@ -17,4 +34,30 @@ public class UnitUtils {
} }
} }
/**
*
* @param distance Distance in meters
* @return String in preferred unit
*/
public static String getDistance(int distance){
if(distance >= 1000){
return getDistanceInKilometers((double)distance);
}else{
return getDistanceInMeters(distance);
}
}
public static String getDistanceInMeters(int distance){
return distance + "m";
}
public static String getDistanceInKilometers(double distance){
return round(distance / 1000, 1) + "km";
}
public static double round(double d, int count){
return (double)Math.round(d * Math.pow(10, count)) / count;
}
} }

View File

@ -0,0 +1,28 @@
<!--
~ 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/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
android:fillColor="@android:color/white"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,9 +1,61 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <!--
xmlns:app="http://schemas.android.com/apk/res-auto" ~ 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/>.
-->
<RelativeLayout 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=".RecordWorkoutActivity"> tools:context=".RecordWorkoutActivity">
</android.support.constraint.ConstraintLayout> <FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/recordInfoRoot">
<LinearLayout
android:id="@+id/recordMapViewrRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</FrameLayout>
<LinearLayout
android:id="@+id/recordInfoRoot"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_alignParentBottom="true"
android:layout_marginBottom="0dp"
android:padding="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="TextView" />
</LinearLayout>
</RelativeLayout>

View File

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

View File

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

View File

@ -1,4 +1,24 @@
<!--
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
~
~ This file is part of FitoTrack
~
~ FitoTrack is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ FitoTrack is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources> <resources>
<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>
</resources> </resources>