mirror of
https://github.com/russok/FitoTrack.git
synced 2025-10-29 08:42:12 -07:00
Compare commits
160 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be3ce4f1c3 | ||
|
|
d372e5ffff | ||
|
|
428256294a | ||
|
|
e69f40553a | ||
|
|
0d84407165 | ||
|
|
72650fefb4 | ||
|
|
815cd764c1 | ||
|
|
e0c3a860b2 | ||
|
|
32724a58f2 | ||
|
|
6cf233a5ea | ||
|
|
5adc2a0536 | ||
|
|
4a89e95b41 | ||
|
|
16c42ec350 | ||
|
|
3f7d590ded | ||
|
|
289e25949f | ||
|
|
8665d5ca14 | ||
|
|
e6ab3ab871 | ||
|
|
4a27bdba26 | ||
|
|
62570fed27 | ||
|
|
a336015818 | ||
|
|
3d930cc49e | ||
|
|
fe181b5fe4 | ||
|
|
3a22c97018 | ||
|
|
bf6c5d0923 | ||
|
|
a26d6a748b | ||
|
|
1ed129778b | ||
|
|
e978e8f6c2 | ||
|
|
e5f3f9f0f7 | ||
|
|
91d97ae805 | ||
|
|
5f84e57bf3 | ||
|
|
71033df2c0 | ||
|
|
18d04a53d3 | ||
|
|
fbaab9cb1b | ||
|
|
daf3e3fb31 | ||
|
|
af05b04acb | ||
|
|
1787e30dfa | ||
|
|
cd15b57768 | ||
|
|
ee657aff06 | ||
|
|
d628a43a84 | ||
|
|
a110964006 | ||
|
|
cde4eb5940 | ||
|
|
e397bc502b | ||
|
|
c83ffd8b4c | ||
|
|
112ea2d9f6 | ||
|
|
782be62f2b | ||
|
|
15c16ab09d | ||
|
|
2d776a3864 | ||
|
|
d75e84d8d9 | ||
|
|
de95affa50 | ||
|
|
f71c75f180 | ||
|
|
a36fc3ae71 | ||
|
|
1bd4be8356 | ||
|
|
6f803f62e4 | ||
|
|
20d0b859a2 | ||
|
|
fb2f78b9ee | ||
|
|
f1a8470a1d | ||
|
|
3123e3badb | ||
|
|
d6dc49ae84 | ||
|
|
4efa078169 | ||
|
|
baf529f9ca | ||
|
|
9089689ce3 | ||
|
|
832f6441cb | ||
|
|
59c1781dd4 | ||
|
|
3455702375 | ||
|
|
f0684647c3 | ||
|
|
dfdb2dbac3 | ||
|
|
5e5203848d | ||
|
|
ce06613b28 | ||
|
|
31528743d3 | ||
|
|
a3a4a5c05c | ||
|
|
5bfad8242e | ||
|
|
acbe5abb05 | ||
|
|
4e354a8078 | ||
|
|
819eb937db | ||
|
|
6b224bf264 | ||
|
|
2b1a0057aa | ||
|
|
a9ce6b2fdb | ||
|
|
2df035ef78 | ||
|
|
7da68b6fc7 | ||
|
|
ac3dfe4a7f | ||
|
|
3a0c38fdb4 | ||
|
|
f5d2093402 | ||
|
|
f5f2be0461 | ||
|
|
787e4333f0 | ||
|
|
83c0168108 | ||
|
|
7617809897 | ||
|
|
b9e16bda25 | ||
|
|
a0a1f44ef9 | ||
|
|
7b15571e1b | ||
|
|
db0690a351 | ||
|
|
96be601641 | ||
|
|
5eac86c647 | ||
|
|
a06a9ec30a | ||
|
|
285b76c5d8 | ||
|
|
f6ad799212 | ||
|
|
07b362ddcc | ||
|
|
5fda41149f | ||
|
|
b7c535c03a | ||
|
|
b494868f83 | ||
|
|
dac2853433 | ||
|
|
f385fae7cb | ||
|
|
05d43de1a7 | ||
|
|
a186308ba8 | ||
|
|
4d8f5e53c8 | ||
|
|
d35c8a7428 | ||
|
|
7a61b94696 | ||
|
|
a2354e5725 | ||
|
|
b4afba8a9c | ||
|
|
a042913381 | ||
|
|
02a5cf8bb8 | ||
|
|
c4b3c3d2e2 | ||
|
|
f664372621 | ||
|
|
2a5f0f3164 | ||
|
|
7fec3ef250 | ||
|
|
4f22a6f500 | ||
|
|
b8c4df8551 | ||
|
|
7d9d97bab7 | ||
|
|
c090479d26 | ||
|
|
607850b8c6 | ||
|
|
fa0a9ea095 | ||
|
|
e28a2ad9c2 | ||
|
|
5654955c42 | ||
|
|
9c3e4021f8 | ||
|
|
f66a22d1c1 | ||
|
|
82e383e953 | ||
|
|
99182791f9 | ||
|
|
e2e0efea32 | ||
|
|
c21214fba9 | ||
|
|
b8a0012f7d | ||
|
|
ec73699411 | ||
|
|
d488ed5148 | ||
|
|
0a66041b39 | ||
|
|
4f1e84bf02 | ||
|
|
8ce0dacb67 | ||
|
|
2177a89198 | ||
|
|
9c17d19308 | ||
|
|
dff7187bf7 | ||
|
|
e9d0e3ad3d | ||
|
|
689cb73818 | ||
|
|
b9278e0299 | ||
|
|
393fd4aba9 | ||
|
|
4b4cb52138 | ||
|
|
a7d683ad4e | ||
|
|
fb194d1031 | ||
|
|
2ad3ca4880 | ||
|
|
744f4389d6 | ||
|
|
179e271cbf | ||
|
|
6369e1a434 | ||
|
|
1e789a6b67 | ||
|
|
aac80d6798 | ||
|
|
9c4d06fe53 | ||
|
|
7d2e3a063d | ||
|
|
eae7bfb9d9 | ||
|
|
91e3c9ee4f | ||
|
|
b7dc876dbb | ||
|
|
4df7e6ab08 | ||
|
|
062e785673 | ||
|
|
da18ef792d | ||
|
|
35dbe1df28 | ||
|
|
9942be1877 |
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
/app/build
|
||||
/app/release
|
||||
|
||||
# misc
|
||||
.*
|
||||
!.gitignore
|
||||
|
||||
# idea
|
||||
*.iml
|
||||
|
||||
# gradle
|
||||
local.properties
|
||||
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,9 +1 @@
|
||||
## 1.0
|
||||
|
||||
* Record workouts
|
||||
* List workouts
|
||||
* Show workout data (map, speed diagram, energy) + Delete workouts
|
||||
* Import/Export data
|
||||
* Export Workouts as GPX
|
||||
* Change units
|
||||
* Change weight
|
||||
*please see [Releases](https://codeberg.org/jannis/FitoTrack/releases).*
|
||||
49
NOTICE.md
49
NOTICE.md
@ -24,6 +24,7 @@
|
||||
Current owners and lead developers are: Ludwig Brinckmann, devemux86
|
||||
|
||||
<https://github.com/PhilJay/MPAndroidChart>
|
||||
|
||||
Copyright 2019 Philipp Jahoda
|
||||
|
||||
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
|
||||
@ -33,6 +34,7 @@
|
||||
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.
|
||||
|
||||
<https://github.com/Clans/FloatingActionButton>
|
||||
|
||||
Copyright 2015 Dmytro Tarianyk
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -47,18 +49,55 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
<https://github.com/jenetics/jpx>
|
||||
<https://github.com/vectorstofinal/geoid_heights>
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 vectorstofinal
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
<https://github.com/westnordost/osmapi>
|
||||
|
||||
© 2016-2019 Tobias Zwick. This library is released under the terms of the GNU Lesser General Public License (LGPL).
|
||||
|
||||
<https://github.com/mttkay/signpost>
|
||||
|
||||
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
|
||||
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.
|
||||
limitations under the License.
|
||||
|
||||
<https://github.com/FasterXML/jackson-core>
|
||||
|
||||
This copy of Jackson JSON processor streaming parser/generator is licensed under the
|
||||
Apache (Software) License, version 2.0 ("the License").
|
||||
See the License for details about distribution rights, and the
|
||||
specific rights regarding derivate works.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
31
README.md
31
README.md
@ -1,9 +1,11 @@
|
||||
# FitoTrack
|
||||
|
||||
FitoTrack is a mobile app for logging and viewing your workouts. Whether you're running, cycling or hiking FitoTrack will show you give you the most important information with detailed charts and statistics.
|
||||
It is open-source and completely ad-free.
|
||||
FitoTrack is a mobile app for logging and viewing your workouts. Whether you're running, cycling or hiking, FitoTrack will show you the most important information, with detailed charts and statistics. It is open-source and completely ad-free.
|
||||
|
||||
(download links)
|
||||
<p align="center">
|
||||
<a href="https://play.google.com/store/apps/details?id=de.tadris.fitness"><img alt="Get it on Google Play" src="https://codeberg.org/jannis/FitoTrack/raw/branch/master/doc/badge-google-play.png" height="75"/></a>
|
||||
<a href="https://f-droid.org/packages/de.tadris.fitness"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="75"></a>
|
||||
</p>
|
||||
|
||||
## Screenshots
|
||||
|
||||
@ -14,28 +16,23 @@ It is open-source and completely ad-free.
|
||||
|
||||
## Features
|
||||
|
||||
- **Track workouts.** Choose your sports type and just start running/cycling/hiking/etc. You can see general information right below the map on the track screen.
|
||||
- **View your workouts.** View general information like date, time, duration, distance, speed, pace. See your route on a map. Investigate how you performed in the speed diagram.
|
||||
- **Open-Source.** There is no advertivesment, no tracking and the source code is open and licensed under the GPLv3 .
|
||||
- **Track workouts.** Choose the type of sport you would like to track and just start running, cycling or hiking, for example. You can see the general information right below the map on the tracking screen.
|
||||
- **View your workouts.** View general information such as date, time, duration, distance, speed and pace. See your route on a map. Work out your level of performance from the speed diagram.
|
||||
- **Open-Source.** There is neither advertivesment nor tracking, and the source code is open and licensed under the GPLv3.
|
||||
|
||||
see [Roadmap](https://codeberg.org/jannis/FitoTrack/wiki/Roadmap) for more planned features
|
||||
|
||||
|
||||
## Installing
|
||||
|
||||
...
|
||||
Please see the [Userguide](https://codeberg.org/jannis/FitoTrack/wiki/How-to-use) if you have any problems.
|
||||
|
||||
## Contributing
|
||||
|
||||
You find the app useful and want to contribute to make it even better? There are many ways and here are some of them listed:
|
||||
You find the app useful and want to contribute to make it even better? Here are some ways you can help:
|
||||
|
||||
* **Create issues.** As very simple way of contributing to the project is to report crashes and bugs and also suggest features. You can join the [Telegram Group](https://t.me/fitotrack) and share your ideas about the app.
|
||||
* **Share the app.** Tell your friends, family, colleagues about the app. In real life and online. You could for example write a post about FitoTrack on your favorite social media network.
|
||||
* **Write code.** If you're able to write code for Android you can contact me via the [Telegram Group](https://t.me/fitotrack).
|
||||
* **Report issues.** A very simple way of contributing to the project is to report crashes and bugs, as well as suggest possible new features. You can join the [Telegram Group](https://t.me/fitotrack) and share your ideas regarding the app.
|
||||
* **Share the app.** Tell your friends, family, colleagues about the app, in real life and online. You could, for example, write a post about FitoTrack on your favorite social media networks.
|
||||
* **Write code.** If you're able to write code for Android or just want to help with translations, please refer to the [Contribiution](https://codeberg.org/jannis/FitoTrack/wiki/Contributing) site in the project wiki.
|
||||
|
||||
### Donate
|
||||
|
||||
When I started programming this app I decided that it should be completely free and open-source. This also means for me that there will be no income from ads or paid features. So if you enjoy the app and want to support my work you are able to do so:
|
||||
When I started programming this app I decided that it should be completely free and open-source. This also means that there will be no income for me from ads or paid features. So if you enjoy the app and want to support my work you are able to do so:
|
||||
|
||||
* BTC: 3BSbGRbJaiSxVg3D6nVRK5C1SZEezhjtK5
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -35,8 +35,8 @@ android {
|
||||
applicationId "de.tadris.fitness"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 102
|
||||
versionName "1.0.2"
|
||||
versionCode 500
|
||||
versionName "5.0"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
@ -49,6 +49,12 @@ android {
|
||||
sourceCompatibility = '1.8'
|
||||
targetCompatibility = '1.8'
|
||||
}
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
// Or, if you prefer, you can continue to check for errors in release builds,
|
||||
// but continue the build even when errors are found:
|
||||
// abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -67,15 +73,26 @@ dependencies {
|
||||
implementation 'org.mapsforge:mapsforge-map-android:0.11.0'
|
||||
implementation 'com.caverock:androidsvg:1.3'
|
||||
|
||||
// UI
|
||||
implementation 'net.sf.kxml:kxml2:2.3.0'
|
||||
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
|
||||
implementation 'com.github.clans:fab:1.6.4'
|
||||
|
||||
// XML
|
||||
implementation 'stax:stax-api:1.0.1'
|
||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8'
|
||||
|
||||
// Android Room
|
||||
def room_version = "2.2.0-alpha02"
|
||||
// File Utils
|
||||
implementation 'commons-io:commons-io:2.6'
|
||||
|
||||
// Upload to OSM
|
||||
implementation('de.westnordost:osmapi-traces:1.0')
|
||||
configurations {
|
||||
compile.exclude group: 'net.sf.kxml', module: 'kxml2' // already included in Android
|
||||
}
|
||||
|
||||
// Android Room Database
|
||||
def room_version = "2.2.0"
|
||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
|
||||
|
||||
@ -1,29 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2020 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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
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.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:appCategory="productivity"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity android:name=".activity.SettingsActivity"></activity>
|
||||
<activity android:name=".activity.VoiceAnnouncementsSettingsActivity" />
|
||||
<activity android:name=".activity.SettingsActivity" />
|
||||
<activity android:name=".activity.EnterWorkoutActivity"/>
|
||||
<activity
|
||||
android:name=".activity.ShowWorkoutMapActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".activity.ShowWorkoutMapDiagramActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".activity.ShowWorkoutActivity"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".activity.RecordWorkoutActivity"
|
||||
android:showOnLockScreen="true"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity
|
||||
android:name=".activity.ListWorkoutsActivity"
|
||||
@ -37,16 +68,19 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="de.tadris.fitness.fileprovider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepaths" />
|
||||
</provider>
|
||||
<service android:name=".location.LocationListener"></service>
|
||||
|
||||
<service android:name=".recording.LocationListener" />
|
||||
<service android:name=".recording.PressureService" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -21,19 +21,23 @@ package de.tadris.fitness;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Room;
|
||||
import androidx.room.migration.Migration;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.tadris.fitness.data.AppDatabase;
|
||||
import de.tadris.fitness.data.UserPreferences;
|
||||
import de.tadris.fitness.location.LocationListener;
|
||||
import de.tadris.fitness.recording.LocationListener;
|
||||
import de.tadris.fitness.util.FitoTrackThemes;
|
||||
import de.tadris.fitness.util.unit.UnitUtils;
|
||||
|
||||
public class Instance {
|
||||
|
||||
public static final String DATABASE_NAME= "fito-track";
|
||||
private static final String DATABASE_NAME = "fito-track";
|
||||
|
||||
private static Instance instance;
|
||||
|
||||
@ -44,13 +48,64 @@ public class Instance {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public AppDatabase db;
|
||||
public List<LocationListener.LocationChangeListener> locationChangeListeners= new ArrayList<>();
|
||||
public UserPreferences userPreferences;
|
||||
public final AppDatabase db;
|
||||
public final List<LocationListener.LocationChangeListener> locationChangeListeners = new ArrayList<>();
|
||||
public final UserPreferences userPreferences;
|
||||
public final FitoTrackThemes themes;
|
||||
|
||||
public boolean pressureAvailable= false;
|
||||
public float lastPressure= 0;
|
||||
|
||||
private Instance(Context context) {
|
||||
userPreferences= new UserPreferences(context);
|
||||
themes = new FitoTrackThemes(context);
|
||||
db = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
|
||||
.addMigrations(new Migration(1, 2) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
try {
|
||||
database.beginTransaction();
|
||||
|
||||
database.execSQL("ALTER table workout add descent REAL NOT NULL DEFAULT 0;");
|
||||
database.execSQL("ALTER table workout add ascent REAL NOT NULL DEFAULT 0");
|
||||
|
||||
database.execSQL("ALTER TABLE workout_sample RENAME TO workout_sample2;");
|
||||
|
||||
database.execSQL("CREATE TABLE workout_sample (" +
|
||||
"id INTEGER NOT NULL DEFAULT NULL PRIMARY KEY," +
|
||||
"relativeTime INTEGER NOT NULL DEFAULT NULL," +
|
||||
"elevation REAL NOT NULL DEFAULT NULL," +
|
||||
"absoluteTime INTEGER NOT NULL DEFAULT NULL," +
|
||||
"lat REAL NOT NULL DEFAULT NULL," +
|
||||
"lon REAL NOT NULL DEFAULT NULL," +
|
||||
"speed REAL NOT NULL DEFAULT NULL," +
|
||||
"workout_id INTEGER NOT NULL DEFAULT NULL," +
|
||||
"FOREIGN KEY (workout_id) REFERENCES workout(id) ON DELETE CASCADE);");
|
||||
|
||||
database.execSQL("INSERT INTO workout_sample (id, relativeTime, elevation, absoluteTime, lat, lon, speed, workout_id) " +
|
||||
"SELECT id, relativeTime, elevation, absoluteTime, lat, lon, speed, workout_id FROM workout_sample2");
|
||||
|
||||
database.execSQL("DROP TABLE workout_sample2");
|
||||
|
||||
database.setTransactionSuccessful();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
}
|
||||
}
|
||||
}, new Migration(2, 3) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
try {
|
||||
database.beginTransaction();
|
||||
|
||||
database.execSQL("ALTER table workout add COLUMN edited INTEGER not null default 0");
|
||||
|
||||
database.setTransactionSuccessful();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
}
|
||||
}
|
||||
})
|
||||
.allowMainThreadQueries()
|
||||
.build();
|
||||
UnitUtils.setUnit(context);
|
||||
|
||||
@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.util.TypedValue;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.data.WorkoutBuilder;
|
||||
import de.tadris.fitness.data.WorkoutType;
|
||||
import de.tadris.fitness.dialog.DatePickerFragment;
|
||||
import de.tadris.fitness.dialog.DurationPickerDialogFragment;
|
||||
import de.tadris.fitness.dialog.SelectWorkoutTypeDialog;
|
||||
import de.tadris.fitness.dialog.TimePickerFragment;
|
||||
import de.tadris.fitness.util.unit.UnitUtils;
|
||||
|
||||
public class EnterWorkoutActivity extends InformationActivity implements SelectWorkoutTypeDialog.WorkoutTypeSelectListener, DatePickerFragment.DatePickerCallback, TimePickerFragment.TimePickerCallback, DurationPickerDialogFragment.DurationPickListener {
|
||||
|
||||
WorkoutBuilder workoutBuilder = new WorkoutBuilder();
|
||||
TextView typeTextView, dateTextView, timeTextView, durationTextView;
|
||||
EditText distanceEditText, commentEditText;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_enter_workout);
|
||||
|
||||
initRoot();
|
||||
|
||||
addTitle(getString(R.string.info));
|
||||
setupActionBar();
|
||||
|
||||
KeyValueLine typeLine = addKeyValueLine(getString(R.string.type));
|
||||
typeTextView = typeLine.value;
|
||||
typeLine.lineRoot.setOnClickListener(v -> showTypeSelection());
|
||||
|
||||
distanceEditText = new EditText(this);
|
||||
distanceEditText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||
distanceEditText.setSingleLine(true);
|
||||
distanceEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
distanceEditText.setOnEditorActionListener((v, actionId, event) -> {
|
||||
// If the User clicks on the finish button on the keyboard, continue by showing the date selection
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH ||
|
||||
actionId == EditorInfo.IME_ACTION_DONE ||
|
||||
event != null &&
|
||||
event.getAction() == KeyEvent.ACTION_DOWN &&
|
||||
event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
|
||||
if (event == null || !event.isShiftPressed()) {
|
||||
showDateSelection();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
addKeyValueLine(getString(R.string.workoutDistance), distanceEditText, UnitUtils.CHOSEN_SYSTEM.getLongDistanceUnit());
|
||||
|
||||
|
||||
KeyValueLine dateLine = addKeyValueLine(getString(R.string.workoutDate));
|
||||
dateLine.lineRoot.setOnClickListener(v -> showDateSelection());
|
||||
dateTextView = dateLine.value;
|
||||
|
||||
KeyValueLine timeLine = addKeyValueLine(getString(R.string.workoutStartTime));
|
||||
timeLine.lineRoot.setOnClickListener(v -> showTimeSelection());
|
||||
timeTextView = timeLine.value;
|
||||
|
||||
KeyValueLine durationLine = addKeyValueLine(getString(R.string.workoutDuration));
|
||||
durationLine.lineRoot.setOnClickListener(v -> showDurationSelection());
|
||||
durationTextView = durationLine.value;
|
||||
|
||||
addTitle(getString(R.string.comment));
|
||||
|
||||
commentEditText = new EditText(this);
|
||||
commentEditText.setSingleLine(true);
|
||||
root.addView(commentEditText);
|
||||
|
||||
updateTextViews();
|
||||
}
|
||||
|
||||
private void saveWorkout() {
|
||||
workoutBuilder.setComment(commentEditText.getText().toString());
|
||||
try {
|
||||
workoutBuilder.setLength((int) (Double.parseDouble(distanceEditText.getText().toString()) * 1000));
|
||||
} catch (NumberFormatException ignored) {
|
||||
distanceEditText.requestFocus();
|
||||
distanceEditText.setError(getString(R.string.errorEnterValidNumber));
|
||||
return;
|
||||
}
|
||||
if (workoutBuilder.getStart().getTimeInMillis() > System.currentTimeMillis()) {
|
||||
Toast.makeText(this, R.string.errorWorkoutAddFuture, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
if (workoutBuilder.getDuration() < 1000) {
|
||||
Toast.makeText(this, R.string.errorEnterValidDuration, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
ShowWorkoutActivity.selectedWorkout = workoutBuilder.insertWorkout(this);
|
||||
startActivity(new Intent(this, ShowWorkoutActivity.class));
|
||||
finish();
|
||||
}
|
||||
|
||||
private void updateTextViews() {
|
||||
typeTextView.setText(getString(workoutBuilder.getWorkoutType().title));
|
||||
dateTextView.setText(SimpleDateFormat.getDateInstance().format(workoutBuilder.getStart().getTime()));
|
||||
timeTextView.setText(SimpleDateFormat.getTimeInstance().format(workoutBuilder.getStart().getTime()));
|
||||
durationTextView.setText(UnitUtils.getHourMinuteSecondTime(workoutBuilder.getDuration()));
|
||||
}
|
||||
|
||||
private void showTypeSelection() {
|
||||
new SelectWorkoutTypeDialog(this, this).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectWorkoutType(WorkoutType workoutType) {
|
||||
workoutBuilder.setWorkoutType(workoutType);
|
||||
updateTextViews();
|
||||
distanceEditText.requestFocus();
|
||||
}
|
||||
|
||||
private void showDateSelection() {
|
||||
DatePickerFragment fragment = new DatePickerFragment();
|
||||
fragment.callback = this;
|
||||
fragment.show(getFragmentManager(), "datePicker");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDatePick(int year, int month, int day) {
|
||||
workoutBuilder.getStart().set(year, month, day);
|
||||
updateTextViews();
|
||||
showTimeSelection();
|
||||
}
|
||||
|
||||
private void showTimeSelection() {
|
||||
TimePickerFragment fragment = new TimePickerFragment();
|
||||
fragment.callback = this;
|
||||
fragment.show(getFragmentManager(), "timePicker");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimePick(int hour, int minute) {
|
||||
workoutBuilder.getStart().set(Calendar.HOUR_OF_DAY, hour);
|
||||
workoutBuilder.getStart().set(Calendar.MINUTE, minute);
|
||||
workoutBuilder.getStart().set(Calendar.SECOND, 0);
|
||||
updateTextViews();
|
||||
showDurationSelection();
|
||||
}
|
||||
|
||||
private void showDurationSelection() {
|
||||
DurationPickerDialogFragment fragment = new DurationPickerDialogFragment(this, this, workoutBuilder.getDuration());
|
||||
fragment.listener = this;
|
||||
fragment.initialDuration = workoutBuilder.getDuration();
|
||||
fragment.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDurationPick(long duration) {
|
||||
workoutBuilder.setDuration(duration);
|
||||
updateTextViews();
|
||||
commentEditText.requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
getMenuInflater().inflate(R.menu.enter_workout_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
switch (id) {
|
||||
case R.id.actionEnterWorkoutAdd:
|
||||
saveWorkout();
|
||||
return true;
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void setupActionBar() {
|
||||
if (getActionBar() != null) {
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void initRoot() {
|
||||
root = findViewById(R.id.enterWorkoutRoot);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -19,48 +19,35 @@
|
||||
|
||||
package de.tadris.fitness.activity;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.R;
|
||||
|
||||
abstract public class FitoTrackActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setTheme(Instance.getInstance(this).themes.getDefaultTheme());
|
||||
}
|
||||
|
||||
|
||||
protected int getThemePrimaryColor() {
|
||||
int getThemePrimaryColor() {
|
||||
final TypedValue value = new TypedValue ();
|
||||
getTheme().resolveAttribute (android.R.attr.colorPrimary, value, true);
|
||||
return value.data;
|
||||
}
|
||||
|
||||
protected void shareFile(Uri uri){
|
||||
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
|
||||
intentShareFile.setDataAndType(uri, getContentResolver().getType(uri));
|
||||
intentShareFile.putExtra(Intent.EXTRA_STREAM, uri);
|
||||
intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
startActivity(Intent.createChooser(intentShareFile, getString(R.string.shareFile)));
|
||||
|
||||
Log.d("Export", uri.toString());
|
||||
Log.d("Export", getContentResolver().getType(uri));
|
||||
try {
|
||||
Log.d("Export", new BufferedInputStream(getContentResolver().openInputStream(uri)).toString());
|
||||
} catch (FileNotFoundException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected void showErrorDialog(Exception e, @StringRes int title, @StringRes int message){
|
||||
void showErrorDialog(Exception e, @StringRes int title, @StringRes int message) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(title)
|
||||
.setMessage(getString(message) + "\n\n" + e.getMessage())
|
||||
@ -68,5 +55,16 @@ abstract public class FitoTrackActivity extends Activity {
|
||||
.create().show();
|
||||
}
|
||||
|
||||
protected void requestStoragePermissions() {
|
||||
if (!hasStoragePermission()) {
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasStoragePermission() {
|
||||
return ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
|
||||
ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.AlertDialog;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.RingtonePreference;
|
||||
import android.text.TextUtils;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.util.unit.UnitUtils;
|
||||
|
||||
public abstract class FitoTrackSettingsActivity extends PreferenceActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
setTheme(Instance.getInstance(this).themes.getDefaultTheme());
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
protected void showErrorDialog(Exception e, @StringRes int title, @StringRes int message) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(title)
|
||||
.setMessage(getString(message) + "\n\n" + e.getMessage())
|
||||
.setPositiveButton(R.string.okay, null)
|
||||
.create().show();
|
||||
}
|
||||
|
||||
/**
|
||||
* A preference value change listener that updates the preference's summary
|
||||
* to reflect its new value.
|
||||
*/
|
||||
protected static final Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = (preference, value) -> {
|
||||
String stringValue = value.toString();
|
||||
|
||||
if (preference instanceof ListPreference) {
|
||||
// For list preferences, look up the correct display value in
|
||||
// the preference's 'entries' list.
|
||||
ListPreference listPreference = (ListPreference) preference;
|
||||
int index = listPreference.findIndexOfValue(stringValue);
|
||||
|
||||
// Set the summary to reflect the new value.
|
||||
preference.setSummary(
|
||||
index >= 0
|
||||
? listPreference.getEntries()[index]
|
||||
: null);
|
||||
|
||||
} else if (preference instanceof RingtonePreference) {
|
||||
// For ringtone preferences, look up the correct display value
|
||||
// using RingtoneManager.
|
||||
if (TextUtils.isEmpty(stringValue)) {
|
||||
// Empty values correspond to 'silent' (no ringtone).
|
||||
preference.setSummary(R.string.pref_ringtone_silent);
|
||||
|
||||
} else {
|
||||
Ringtone ringtone = RingtoneManager.getRingtone(
|
||||
preference.getContext(), Uri.parse(stringValue));
|
||||
|
||||
if (ringtone == null) {
|
||||
// Clear the summary if there was a lookup error.
|
||||
preference.setSummary(null);
|
||||
} else {
|
||||
// Set the summary to reflect the new ringtone display
|
||||
// name.
|
||||
String name = ringtone.getTitle(preference.getContext());
|
||||
preference.setSummary(name);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// For all other preferences, set the summary to the value's
|
||||
// simple string representation.
|
||||
preference.setSummary(stringValue);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
protected static void bindPreferenceSummaryToValue(Preference preference) {
|
||||
// Set the listener to watch for value changes.
|
||||
preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
|
||||
|
||||
// Trigger the listener immediately with the preference's
|
||||
// current value.
|
||||
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
|
||||
PreferenceManager
|
||||
.getDefaultSharedPreferences(preference.getContext())
|
||||
.getString(preference.getKey(), ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
UnitUtils.setUnit(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onMenuItemSelected(featureId, item);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.graphics.Typeface;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
|
||||
public abstract class InformationActivity extends FitoTrackActivity {
|
||||
|
||||
ViewGroup root;
|
||||
|
||||
protected void addTitle(String title) {
|
||||
TextView textView = new TextView(this);
|
||||
textView.setText(title);
|
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||
textView.setTextColor(getThemePrimaryColor());
|
||||
textView.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
textView.setAllCaps(true);
|
||||
textView.setPadding(0, 20, 0, 0);
|
||||
|
||||
root.addView(textView);
|
||||
}
|
||||
|
||||
protected TextView addText(String text, boolean themeColor) {
|
||||
TextView textView = createTextView(text, themeColor);
|
||||
root.addView(textView);
|
||||
|
||||
return textView;
|
||||
}
|
||||
|
||||
protected TextView createTextView(String text) {
|
||||
return createTextView(text, false);
|
||||
}
|
||||
|
||||
protected TextView createTextView(String text, boolean themeColor) {
|
||||
TextView textView = new TextView(this);
|
||||
textView.setText(text);
|
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||
if (themeColor) {
|
||||
textView.setTextColor(getThemePrimaryColor());
|
||||
} else {
|
||||
textView.setTextColor(getResources().getColor(R.color.textLighterBlack));
|
||||
}
|
||||
textView.setPadding(0, 20, 0, 0);
|
||||
|
||||
return textView;
|
||||
}
|
||||
|
||||
protected void addKeyValue(String key1, String value1) {
|
||||
addKeyValue(key1, value1, "", "");
|
||||
}
|
||||
|
||||
protected void addKeyValue(String key1, String value1, String key2, String value2) {
|
||||
View v = getLayoutInflater().inflate(R.layout.show_entry, root, false);
|
||||
|
||||
TextView title1 = v.findViewById(R.id.v1title);
|
||||
TextView title2 = v.findViewById(R.id.v2title);
|
||||
TextView v1 = v.findViewById(R.id.v1value);
|
||||
TextView v2 = v.findViewById(R.id.v2value);
|
||||
|
||||
title1.setText(key1);
|
||||
title2.setText(key2);
|
||||
v1.setText(value1);
|
||||
v2.setText(value2);
|
||||
|
||||
root.addView(v);
|
||||
}
|
||||
|
||||
protected KeyValueLine addKeyValueLine(String key) {
|
||||
return addKeyValueLine(key, "");
|
||||
}
|
||||
|
||||
protected KeyValueLine addKeyValueLine(String key, String value) {
|
||||
return addKeyValueLine(key, null, value);
|
||||
}
|
||||
|
||||
protected KeyValueLine addKeyValueLine(String key, @Nullable View view) {
|
||||
return addKeyValueLine(key, view, "");
|
||||
}
|
||||
|
||||
protected KeyValueLine addKeyValueLine(String key, @Nullable View view, String value) {
|
||||
View v = getLayoutInflater().inflate(R.layout.enter_workout_line, root, false);
|
||||
|
||||
TextView keyView = v.findViewById(R.id.lineKey);
|
||||
TextView valueView = v.findViewById(R.id.lineValue);
|
||||
LinearLayout customViewRoot = v.findViewById(R.id.lineViewRoot);
|
||||
|
||||
keyView.setText(key);
|
||||
valueView.setText(value);
|
||||
|
||||
if (view != null) {
|
||||
customViewRoot.addView(view);
|
||||
}
|
||||
|
||||
root.addView(v);
|
||||
return new KeyValueLine(v, keyView, valueView, view);
|
||||
}
|
||||
|
||||
public static class KeyValueLine {
|
||||
public View lineRoot;
|
||||
public TextView key;
|
||||
public TextView value;
|
||||
public View customView;
|
||||
|
||||
public KeyValueLine(View lineRoot, TextView key, TextView value, View customView) {
|
||||
this.lineRoot = lineRoot;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.customView = customView;
|
||||
}
|
||||
}
|
||||
|
||||
abstract void initRoot();
|
||||
|
||||
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -42,12 +42,12 @@ public class LauncherActivity extends Activity {
|
||||
new Handler().postDelayed(this::init, 100);
|
||||
}
|
||||
|
||||
void init(){
|
||||
private void init() {
|
||||
Instance.getInstance(this);
|
||||
start();
|
||||
}
|
||||
|
||||
void start(){
|
||||
private void start() {
|
||||
startActivity(new Intent(this, ListWorkoutsActivity.class));
|
||||
finish();
|
||||
overridePendingTransition(R.anim.fade_in, R.anim.stay);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -19,7 +19,6 @@
|
||||
|
||||
package de.tadris.fitness.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
@ -37,15 +36,17 @@ import com.github.clans.fab.FloatingActionMenu;
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.data.WorkoutType;
|
||||
import de.tadris.fitness.util.DialogUtils;
|
||||
import de.tadris.fitness.view.WorkoutAdapter;
|
||||
|
||||
public class ListWorkoutsActivity extends Activity implements WorkoutAdapter.WorkoutAdapterListener {
|
||||
public class ListWorkoutsActivity extends FitoTrackActivity implements WorkoutAdapter.WorkoutAdapterListener {
|
||||
|
||||
private RecyclerView listView;
|
||||
private RecyclerView.Adapter adapter;
|
||||
private RecyclerView.LayoutManager layoutManager;
|
||||
private FloatingActionMenu menu;
|
||||
Workout[] workouts;
|
||||
private Workout[] workouts;
|
||||
|
||||
|
||||
@Override
|
||||
@ -62,19 +63,21 @@ public class ListWorkoutsActivity extends Activity implements WorkoutAdapter.Wor
|
||||
menu= findViewById(R.id.workoutListMenu);
|
||||
menu.setOnMenuButtonLongClickListener(v -> {
|
||||
if(workouts.length > 0){
|
||||
startRecording(workouts[0].workoutType);
|
||||
startRecording(workouts[0].getWorkoutType());
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.workoutListRecordRunning).setOnClickListener(v -> startRecording(Workout.WORKOUT_TYPE_RUNNING));
|
||||
findViewById(R.id.workoutListRecordHiking) .setOnClickListener(v -> startRecording(Workout.WORKOUT_TYPE_HIKING));
|
||||
findViewById(R.id.workoutListRecordCycling).setOnClickListener(v -> startRecording(Workout.WORKOUT_TYPE_CYCLING));
|
||||
findViewById(R.id.workoutListRecordRunning).setOnClickListener(v -> startRecording(WorkoutType.RUNNING));
|
||||
findViewById(R.id.workoutListRecordHiking).setOnClickListener(v -> startRecording(WorkoutType.HIKING));
|
||||
findViewById(R.id.workoutListRecordCycling).setOnClickListener(v -> startRecording(WorkoutType.CYCLING));
|
||||
findViewById(R.id.workoutListEnter).setOnClickListener(v -> startEnterWorkoutActivity());
|
||||
|
||||
checkFirstStart();
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
private void checkFirstStart(){
|
||||
@ -90,7 +93,13 @@ public class ListWorkoutsActivity extends Activity implements WorkoutAdapter.Wor
|
||||
}
|
||||
}
|
||||
|
||||
public void startRecording(String activity){
|
||||
private void startEnterWorkoutActivity() {
|
||||
menu.close(true);
|
||||
final Intent intent = new Intent(this, EnterWorkoutActivity.class);
|
||||
new Handler().postDelayed(() -> startActivity(intent), 300);
|
||||
}
|
||||
|
||||
private void startRecording(WorkoutType activity) {
|
||||
menu.close(true);
|
||||
RecordWorkoutActivity.ACTIVITY= activity;
|
||||
final Intent intent= new Intent(this, RecordWorkoutActivity.class);
|
||||
@ -101,17 +110,37 @@ public class ListWorkoutsActivity extends Activity implements WorkoutAdapter.Wor
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
workouts= Instance.getInstance(this).db.workoutDao().getWorkouts();
|
||||
adapter= new WorkoutAdapter(workouts, this);
|
||||
listView.setAdapter(adapter);
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(Workout workout) {
|
||||
public void onItemClick(int pos, Workout workout) {
|
||||
ShowWorkoutActivity.selectedWorkout= workout;
|
||||
startActivity(new Intent(this, ShowWorkoutActivity.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemLongClick(int pos, Workout workout) {
|
||||
DialogUtils.showDeleteWorkoutDialog(this, () -> {
|
||||
Instance.getInstance(ListWorkoutsActivity.this).db.workoutDao().deleteWorkout(workout);
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
loadData();
|
||||
refreshAdapter();
|
||||
}
|
||||
|
||||
private void loadData(){
|
||||
workouts= Instance.getInstance(this).db.workoutDao().getWorkouts();
|
||||
}
|
||||
|
||||
private void refreshAdapter(){
|
||||
adapter= new WorkoutAdapter(workouts, this);
|
||||
listView.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
@ -123,14 +152,11 @@ public class ListWorkoutsActivity extends Activity implements WorkoutAdapter.Wor
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
|
||||
switch (id){
|
||||
case R.id.actionOpenSettings:
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
return true;
|
||||
if (id == R.id.actionOpenSettings) {
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -20,17 +20,22 @@
|
||||
package de.tadris.fitness.activity;
|
||||
|
||||
import android.Manifest;
|
||||
import android.animation.Animator;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
@ -49,48 +54,62 @@ import java.util.List;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.location.LocationListener;
|
||||
import de.tadris.fitness.location.WorkoutRecorder;
|
||||
import de.tadris.fitness.data.WorkoutType;
|
||||
import de.tadris.fitness.map.MapManager;
|
||||
import de.tadris.fitness.map.tilesource.TileSources;
|
||||
import de.tadris.fitness.util.ThemeManager;
|
||||
import de.tadris.fitness.recording.LocationListener;
|
||||
import de.tadris.fitness.recording.PressureService;
|
||||
import de.tadris.fitness.recording.WorkoutRecorder;
|
||||
import de.tadris.fitness.recording.announcement.AnnouncementGPSStatus;
|
||||
import de.tadris.fitness.recording.announcement.VoiceAnnouncements;
|
||||
import de.tadris.fitness.util.unit.UnitUtils;
|
||||
|
||||
public class RecordWorkoutActivity extends FitoTrackActivity implements LocationListener.LocationChangeListener, WorkoutRecorder.GpsStateChangedListener {
|
||||
public class RecordWorkoutActivity extends FitoTrackActivity implements LocationListener.LocationChangeListener, WorkoutRecorder.WorkoutRecorderListener, VoiceAnnouncements.VoiceAnnouncementCallback {
|
||||
|
||||
public static String ACTIVITY= Workout.WORKOUT_TYPE_RUNNING;
|
||||
public static WorkoutType ACTIVITY = WorkoutType.OTHER;
|
||||
|
||||
MapView mapView;
|
||||
TileDownloadLayer downloadLayer;
|
||||
WorkoutRecorder recorder;
|
||||
Polyline polyline;
|
||||
List<LatLong> latLongList= new ArrayList<>();
|
||||
InfoViewHolder[] infoViews= new InfoViewHolder[4];
|
||||
TextView timeView, gpsStatusView;
|
||||
boolean isResumed= false;
|
||||
private Handler mHandler= new Handler();
|
||||
PowerManager.WakeLock wakeLock;
|
||||
Intent locationListener;
|
||||
private MapView mapView;
|
||||
private TileDownloadLayer downloadLayer;
|
||||
private WorkoutRecorder recorder;
|
||||
private Polyline polyline;
|
||||
private final List<LatLong> latLongList = new ArrayList<>();
|
||||
private final InfoViewHolder[] infoViews = new InfoViewHolder[4];
|
||||
private TextView timeView;
|
||||
private TextView gpsStatusView;
|
||||
private TextView attribution;
|
||||
private View waitingForGPSOverlay;
|
||||
private boolean gpsFound = false;
|
||||
private boolean isResumed = false;
|
||||
private final Handler mHandler = new Handler();
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
private Intent locationListener;
|
||||
private Intent pressureService;
|
||||
private boolean saved= false;
|
||||
|
||||
private VoiceAnnouncements voiceAnnouncements;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setTheme(ThemeManager.getThemeByWorkoutType(ACTIVITY));
|
||||
setTheme(Instance.getInstance(this).themes.getWorkoutTypeTheme(ACTIVITY));
|
||||
setContentView(R.layout.activity_record_workout);
|
||||
|
||||
setTitle(R.string.recordWorkout);
|
||||
|
||||
setupMap();
|
||||
|
||||
((ViewGroup)findViewById(R.id.recordMapViewrRoot)).addView(mapView);
|
||||
((ViewGroup) findViewById(R.id.recordMapViewerRoot)).addView(mapView);
|
||||
waitingForGPSOverlay= findViewById(R.id.recorderWaitingOverlay);
|
||||
waitingForGPSOverlay.setVisibility(View.VISIBLE);
|
||||
|
||||
attribution = findViewById(R.id.recordMapAttribution);
|
||||
|
||||
checkPermissions();
|
||||
|
||||
recorder= new WorkoutRecorder(this, ACTIVITY, this);
|
||||
recorder.start();
|
||||
|
||||
voiceAnnouncements = new VoiceAnnouncements(this, this);
|
||||
|
||||
infoViews[0]= new InfoViewHolder(findViewById(R.id.recordInfo1Title), findViewById(R.id.recordInfo1Value));
|
||||
infoViews[1]= new InfoViewHolder(findViewById(R.id.recordInfo2Title), findViewById(R.id.recordInfo2Value));
|
||||
infoViews[2]= new InfoViewHolder(findViewById(R.id.recordInfo3Title), findViewById(R.id.recordInfo3Value));
|
||||
@ -115,15 +134,30 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location
|
||||
wakeLock.acquire(1000*60*120);
|
||||
}
|
||||
|
||||
private void hideWaitOverlay(){
|
||||
waitingForGPSOverlay.clearAnimation();
|
||||
waitingForGPSOverlay.animate().alpha(0f).setDuration(1000).setListener(new Animator.AnimatorListener() {
|
||||
@Override public void onAnimationStart(Animator animator) { }
|
||||
|
||||
@Override public void onAnimationCancel(Animator animator) { }
|
||||
|
||||
@Override public void onAnimationRepeat(Animator animator) { }
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
waitingForGPSOverlay.setVisibility(View.GONE);
|
||||
}
|
||||
}).start();
|
||||
hideOSMAttribution();
|
||||
}
|
||||
|
||||
private void hideOSMAttribution() {
|
||||
attribution.animate().alpha(0f).setDuration(1000).setStartDelay(5000).start();
|
||||
}
|
||||
|
||||
private void setupMap(){
|
||||
this.mapView= new MapView(this);
|
||||
TileSources.Purpose purpose;
|
||||
if(ACTIVITY.equals(Workout.WORKOUT_TYPE_CYCLING)){
|
||||
purpose= TileSources.Purpose.CYCLING;
|
||||
}else{
|
||||
purpose= TileSources.Purpose.OUTDOOR;
|
||||
}
|
||||
downloadLayer= MapManager.setupMap(mapView, purpose);
|
||||
downloadLayer = MapManager.setupMap(mapView);
|
||||
}
|
||||
|
||||
private void updateLine(){
|
||||
@ -144,9 +178,7 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location
|
||||
try{
|
||||
while (recorder.isActive()){
|
||||
Thread.sleep(1000);
|
||||
if(isResumed){
|
||||
mHandler.post(this::updateDescription);
|
||||
}
|
||||
mHandler.post(this::updateDescription);
|
||||
}
|
||||
}catch (InterruptedException e){
|
||||
e.printStackTrace();
|
||||
@ -154,12 +186,23 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location
|
||||
}).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(Math.min(100d, recorder.getAvgSpeed())));
|
||||
infoViews[3].setText(getString(R.string.workoutPauseDuration), UnitUtils.getHourMinuteSecondTime(recorder.getPauseDuration()));
|
||||
|
||||
private void updateDescription() {
|
||||
long duration = recorder.getDuration();
|
||||
int distanceInMeters = recorder.getDistanceInMeters();
|
||||
final String distanceCaption = getString(R.string.workoutDistance);
|
||||
final String distance = UnitUtils.getDistance(distanceInMeters);
|
||||
final String avgSpeed = UnitUtils.getSpeed(Math.min(100d, recorder.getAvgSpeed()));
|
||||
if (isResumed) {
|
||||
timeView.setText(UnitUtils.getHourMinuteSecondTime(duration));
|
||||
infoViews[0].setText(distanceCaption, distance);
|
||||
infoViews[1].setText(getString(R.string.workoutBurnedEnergy), recorder.getCalories() + " kcal");
|
||||
infoViews[2].setText(getString(R.string.workoutAvgSpeedShort), avgSpeed);
|
||||
infoViews[3].setText(getString(R.string.workoutPauseDuration), UnitUtils.getHourMinuteSecondTime(recorder.getPauseDuration()));
|
||||
}
|
||||
|
||||
voiceAnnouncements.check(recorder);
|
||||
|
||||
}
|
||||
|
||||
private void stop(){
|
||||
@ -208,14 +251,14 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location
|
||||
.create().show();
|
||||
}
|
||||
|
||||
void checkPermissions(){
|
||||
private 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(){
|
||||
private 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;
|
||||
}
|
||||
@ -226,21 +269,44 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location
|
||||
}
|
||||
}
|
||||
|
||||
public void stopListener(){
|
||||
private void stopListener() {
|
||||
stopService(locationListener);
|
||||
stopService(pressureService);
|
||||
}
|
||||
|
||||
public void startListener(){
|
||||
private void startListener() {
|
||||
if(locationListener == null){
|
||||
locationListener= new Intent(this, LocationListener.class);
|
||||
pressureService= new Intent(this, PressureService.class);
|
||||
}else{
|
||||
stopListener();
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(locationListener);
|
||||
startService(pressureService);
|
||||
}else{
|
||||
startService(locationListener);
|
||||
startService(pressureService);
|
||||
}
|
||||
checkGpsStatus();
|
||||
}
|
||||
|
||||
private void checkGpsStatus(){
|
||||
final LocationManager manager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||
|
||||
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)){
|
||||
openDialogNoGps();
|
||||
}
|
||||
}
|
||||
|
||||
private void openDialogNoGps(){
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.noGpsTitle)
|
||||
.setMessage(R.string.noGpsMessage)
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> finish())
|
||||
.setPositiveButton(R.string.enable, (dialog, which) -> startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)))
|
||||
.setCancelable(false)
|
||||
.create().show();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -254,9 +320,15 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
recorder.stop();
|
||||
saveIfNotSaved(); // Important
|
||||
saveIfNotSaved(); // Important to save
|
||||
|
||||
// Clear map
|
||||
mapView.destroyAll();
|
||||
AndroidGraphicFactory.clearResourceMemoryCache();
|
||||
|
||||
// Shutdown TTS
|
||||
voiceAnnouncements.destroy();
|
||||
|
||||
super.onDestroy();
|
||||
if(wakeLock.isHeld()){
|
||||
wakeLock.release();
|
||||
@ -274,10 +346,18 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location
|
||||
|
||||
public void onResume(){
|
||||
super.onResume();
|
||||
enableLockScreenVisibility();
|
||||
downloadLayer.onResume();
|
||||
isResumed= true;
|
||||
}
|
||||
|
||||
private void enableLockScreenVisibility() {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
|
||||
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
|
||||
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
|
||||
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
@ -306,13 +386,33 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location
|
||||
|
||||
@Override
|
||||
public void onGPSStateChanged(WorkoutRecorder.GpsState oldState, WorkoutRecorder.GpsState state) {
|
||||
mHandler.post(() -> gpsStatusView.setTextColor(state.color));
|
||||
mHandler.post(() -> {
|
||||
gpsStatusView.setTextColor(state.color);
|
||||
if(!gpsFound && (state != WorkoutRecorder.GpsState.SIGNAL_LOST)){
|
||||
gpsFound= true;
|
||||
hideWaitOverlay();
|
||||
}
|
||||
|
||||
AnnouncementGPSStatus announcement = new AnnouncementGPSStatus(RecordWorkoutActivity.this);
|
||||
if (announcement.isEnabled()) {
|
||||
if (oldState == WorkoutRecorder.GpsState.SIGNAL_LOST) { // GPS Signal found
|
||||
voiceAnnouncements.speak(announcement.getSpokenGPSFound());
|
||||
} else if (state == WorkoutRecorder.GpsState.SIGNAL_LOST) {
|
||||
voiceAnnouncements.speak(announcement.getSpokenGPSLost());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static class InfoViewHolder{
|
||||
TextView titleView, valueView;
|
||||
@Override
|
||||
public void onVoiceAnnouncementIsReady(boolean available) {
|
||||
}
|
||||
|
||||
public InfoViewHolder(TextView titleView, TextView valueView) {
|
||||
static class InfoViewHolder {
|
||||
final TextView titleView;
|
||||
final TextView valueView;
|
||||
|
||||
InfoViewHolder(TextView titleView, TextView valueView) {
|
||||
this.titleView = titleView;
|
||||
this.valueView = valueView;
|
||||
}
|
||||
@ -323,5 +423,8 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAutoStop() {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -22,133 +22,34 @@ package de.tadris.fitness.activity;
|
||||
import android.Manifest;
|
||||
import android.app.ActionBar;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.RingtonePreference;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.NumberPicker;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.util.export.Exporter;
|
||||
import de.tadris.fitness.util.gpx.GpxExporter;
|
||||
import de.tadris.fitness.export.BackupController;
|
||||
import de.tadris.fitness.export.RestoreController;
|
||||
import de.tadris.fitness.recording.announcement.VoiceAnnouncements;
|
||||
import de.tadris.fitness.util.FileUtils;
|
||||
import de.tadris.fitness.util.unit.UnitUtils;
|
||||
import de.tadris.fitness.view.ProgressDialogController;
|
||||
|
||||
public class SettingsActivity extends PreferenceActivity {
|
||||
public class SettingsActivity extends FitoTrackSettingsActivity {
|
||||
|
||||
|
||||
protected void shareFile(Uri uri){
|
||||
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
|
||||
intentShareFile.setDataAndType(uri, getContentResolver().getType(uri));
|
||||
intentShareFile.putExtra(Intent.EXTRA_STREAM, uri);
|
||||
intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
startActivity(Intent.createChooser(intentShareFile, getString(R.string.shareFile)));
|
||||
|
||||
Log.d("Export", uri.toString());
|
||||
Log.d("Export", getContentResolver().getType(uri));
|
||||
try {
|
||||
Log.d("Export", new BufferedInputStream(getContentResolver().openInputStream(uri)).toString());
|
||||
} catch (FileNotFoundException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected void showErrorDialog(Exception e, @StringRes int title, @StringRes int message){
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(title)
|
||||
.setMessage(getString(message) + "\n\n" + e.getMessage())
|
||||
.setPositiveButton(R.string.okay, null)
|
||||
.create().show();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A preference value change listener that updates the preference's summary
|
||||
* to reflect its new value.
|
||||
*/
|
||||
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = (preference, value) -> {
|
||||
String stringValue = value.toString();
|
||||
|
||||
if (preference instanceof ListPreference) {
|
||||
// For list preferences, look up the correct display value in
|
||||
// the preference's 'entries' list.
|
||||
ListPreference listPreference = (ListPreference) preference;
|
||||
int index = listPreference.findIndexOfValue(stringValue);
|
||||
|
||||
// Set the summary to reflect the new value.
|
||||
preference.setSummary(
|
||||
index >= 0
|
||||
? listPreference.getEntries()[index]
|
||||
: null);
|
||||
|
||||
} else if (preference instanceof RingtonePreference) {
|
||||
// For ringtone preferences, look up the correct display value
|
||||
// using RingtoneManager.
|
||||
if (TextUtils.isEmpty(stringValue)) {
|
||||
// Empty values correspond to 'silent' (no ringtone).
|
||||
preference.setSummary(R.string.pref_ringtone_silent);
|
||||
|
||||
} else {
|
||||
Ringtone ringtone = RingtoneManager.getRingtone(
|
||||
preference.getContext(), Uri.parse(stringValue));
|
||||
|
||||
if (ringtone == null) {
|
||||
// Clear the summary if there was a lookup error.
|
||||
preference.setSummary(null);
|
||||
} else {
|
||||
// Set the summary to reflect the new ringtone display
|
||||
// name.
|
||||
String name = ringtone.getTitle(preference.getContext());
|
||||
preference.setSummary(name);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// For all other preferences, set the summary to the value's
|
||||
// simple string representation.
|
||||
preference.setSummary(stringValue);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
private static void bindPreferenceSummaryToValue(Preference preference) {
|
||||
// Set the listener to watch for value changes.
|
||||
preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
|
||||
|
||||
// Trigger the listener immediately with the preference's
|
||||
// current value.
|
||||
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
|
||||
PreferenceManager
|
||||
.getDefaultSharedPreferences(preference.getContext())
|
||||
.getString(preference.getKey(), ""));
|
||||
}
|
||||
|
||||
private Handler mHandler= new Handler();
|
||||
private final Handler mHandler = new Handler();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -160,22 +61,63 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
addPreferencesFromResource(R.xml.preferences_main);
|
||||
|
||||
bindPreferenceSummaryToValue(findPreference("unitSystem"));
|
||||
bindPreferenceSummaryToValue(findPreference("mapStyle"));
|
||||
bindPreferenceSummaryToValue(findPreference("themeSetting"));
|
||||
findPreference("themeSetting").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, newValue);
|
||||
Toast.makeText(SettingsActivity.this, R.string.hintRestart, Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
});
|
||||
|
||||
findPreference("weight").setOnPreferenceClickListener(preference -> showWeightPicker());
|
||||
findPreference("import").setOnPreferenceClickListener(preference -> showImportDialog());
|
||||
findPreference("export").setOnPreferenceClickListener(preference -> showExportDialog());
|
||||
findPreference("weight").setOnPreferenceClickListener(preference -> {
|
||||
showWeightPicker();
|
||||
return true;
|
||||
});
|
||||
findPreference("speech").setOnPreferenceClickListener(preference -> {
|
||||
checkTTSandShowConfig();
|
||||
return true;
|
||||
});
|
||||
findPreference("import").setOnPreferenceClickListener(preference -> {
|
||||
showImportDialog();
|
||||
return true;
|
||||
});
|
||||
findPreference("export").setOnPreferenceClickListener(preference -> {
|
||||
showExportDialog();
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private boolean showExportDialog(){
|
||||
private VoiceAnnouncements voiceAnnouncements;
|
||||
|
||||
private void checkTTSandShowConfig() {
|
||||
voiceAnnouncements = new VoiceAnnouncements(this, available -> {
|
||||
if (available) {
|
||||
showSpeechConfig();
|
||||
} else {
|
||||
// TextToSpeech is not available
|
||||
Toast.makeText(SettingsActivity.this, R.string.ttsNotAvailable, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
if (voiceAnnouncements != null) {
|
||||
voiceAnnouncements.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showSpeechConfig() {
|
||||
startActivity(new Intent(this, VoiceAnnouncementsSettingsActivity.class));
|
||||
}
|
||||
|
||||
private void showExportDialog() {
|
||||
if (!hasPermission()) {
|
||||
requestPermissions();
|
||||
return;
|
||||
}
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.exportData)
|
||||
.setMessage(R.string.exportDataSummary)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.backup, (dialog, which) -> {
|
||||
exportBackup();
|
||||
}).create().show();
|
||||
return true;
|
||||
.setPositiveButton(R.string.backup, (dialog, which) -> exportBackup()).create().show();
|
||||
}
|
||||
|
||||
private void exportBackup(){
|
||||
@ -184,15 +126,18 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
new Thread(() -> {
|
||||
try{
|
||||
String file= getFilesDir().getAbsolutePath() + "/shared/backup.ftb";
|
||||
new File(file).getParentFile().mkdirs();
|
||||
File parent = new File(file).getParentFile();
|
||||
if (!parent.exists() && !parent.mkdirs()) {
|
||||
throw new IOException("Cannot write");
|
||||
}
|
||||
Uri uri= FileProvider.getUriForFile(getBaseContext(), "de.tadris.fitness.fileprovider", new File(file));
|
||||
|
||||
Exporter.exportData(getBaseContext(), new File(file),
|
||||
(progress, action) -> mHandler.post(() -> dialogController.setProgress(progress, action)));
|
||||
BackupController backupController= new BackupController(getBaseContext(), new File(file), (progress, action) -> mHandler.post(() -> dialogController.setProgress(progress, action)));
|
||||
backupController.exportData();
|
||||
|
||||
mHandler.post(() -> {
|
||||
dialogController.cancel();
|
||||
shareFile(uri);
|
||||
FileUtils.saveOrShareFile(this, uri, "ftb");
|
||||
});
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
@ -204,28 +149,25 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
}).start();
|
||||
}
|
||||
|
||||
private boolean showImportDialog(){
|
||||
private void showImportDialog() {
|
||||
if(!hasPermission()){
|
||||
requestPermissions();
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.importBackup)
|
||||
.setMessage(R.string.importBackupMessage)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.restore, (dialog, which) -> {
|
||||
importBackup();
|
||||
}).create().show();
|
||||
return true;
|
||||
.setPositiveButton(R.string.restore, (dialog, which) -> importBackup()).create().show();
|
||||
}
|
||||
|
||||
void requestPermissions(){
|
||||
private void requestPermissions() {
|
||||
if (!hasPermission()) {
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasPermission(){
|
||||
private boolean hasPermission() {
|
||||
return ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
@ -241,12 +183,10 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case FILE_SELECT_CODE:
|
||||
if (resultCode == RESULT_OK){
|
||||
importBackup(data.getData());
|
||||
}
|
||||
break;
|
||||
if (requestCode == FILE_SELECT_CODE) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
importBackup(data.getData());
|
||||
}
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
@ -256,13 +196,11 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
dialogController.show();
|
||||
new Thread(() -> {
|
||||
try{
|
||||
Exporter.importData(getBaseContext(), uri,
|
||||
RestoreController restoreController= new RestoreController(getBaseContext(), uri,
|
||||
(progress, action) -> mHandler.post(() -> dialogController.setProgress(progress, action)));
|
||||
restoreController.restoreData();
|
||||
|
||||
mHandler.post(() -> {
|
||||
// DO on backup finished
|
||||
dialogController.cancel();
|
||||
});
|
||||
mHandler.post(dialogController::cancel);
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
mHandler.post(() -> {
|
||||
@ -273,7 +211,9 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
}).start();
|
||||
}
|
||||
|
||||
private boolean showWeightPicker() {
|
||||
private void showWeightPicker() {
|
||||
UnitUtils.setUnit(this); // Maybe the user changed unit system
|
||||
|
||||
final AlertDialog.Builder d = new AlertDialog.Builder(this);
|
||||
final SharedPreferences preferences= PreferenceManager.getDefaultSharedPreferences(this);
|
||||
d.setTitle(getString(R.string.pref_weight));
|
||||
@ -282,7 +222,8 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
np.setMaxValue((int) UnitUtils.CHOSEN_SYSTEM.getWeightFromKilogram(150));
|
||||
np.setMinValue((int) UnitUtils.CHOSEN_SYSTEM.getWeightFromKilogram(20));
|
||||
np.setFormatter(value -> value + " " + UnitUtils.CHOSEN_SYSTEM.getWeightUnit());
|
||||
np.setValue(preferences.getInt("weight", 80));
|
||||
final String preferenceVariable = "weight";
|
||||
np.setValue((int)Math.round(UnitUtils.CHOSEN_SYSTEM.getWeightFromKilogram(preferences.getInt(preferenceVariable, 80))));
|
||||
np.setWrapSelectorWheel(false);
|
||||
|
||||
d.setView(v);
|
||||
@ -291,12 +232,10 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
d.setPositiveButton(R.string.okay, (dialog, which) -> {
|
||||
int unitValue= np.getValue();
|
||||
int kilograms= (int)Math.round(UnitUtils.CHOSEN_SYSTEM.getKilogramFromUnit(unitValue));
|
||||
preferences.edit().putInt("weight", kilograms).apply();
|
||||
preferences.edit().putInt(preferenceVariable, kilograms).apply();
|
||||
});
|
||||
|
||||
d.create().show();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -310,26 +249,4 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
UnitUtils.setUnit(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == android.R.id.home) {
|
||||
if (!super.onMenuItemSelected(featureId, item)) {
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onMenuItemSelected(featureId, item);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -20,92 +20,56 @@
|
||||
package de.tadris.fitness.activity;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import com.github.mikephil.charting.charts.LineChart;
|
||||
import com.github.mikephil.charting.components.Description;
|
||||
import com.github.mikephil.charting.data.Entry;
|
||||
import com.github.mikephil.charting.data.LineData;
|
||||
import com.github.mikephil.charting.data.LineDataSet;
|
||||
import com.github.mikephil.charting.highlight.Highlight;
|
||||
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
|
||||
|
||||
import org.mapsforge.core.graphics.Paint;
|
||||
import org.mapsforge.core.model.BoundingBox;
|
||||
import org.mapsforge.core.model.MapPosition;
|
||||
import org.mapsforge.core.util.LatLongUtils;
|
||||
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
|
||||
import org.mapsforge.map.android.view.MapView;
|
||||
import org.mapsforge.map.layer.download.TileDownloadLayer;
|
||||
import org.mapsforge.map.layer.overlay.FixedPixelCircle;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.data.WorkoutManager;
|
||||
import de.tadris.fitness.data.WorkoutSample;
|
||||
import de.tadris.fitness.map.MapManager;
|
||||
import de.tadris.fitness.map.WorkoutLayer;
|
||||
import de.tadris.fitness.map.tilesource.TileSources;
|
||||
import de.tadris.fitness.util.ThemeManager;
|
||||
import de.tadris.fitness.util.WorkoutTypeCalculator;
|
||||
import de.tadris.fitness.osm.OAuthAuthentication;
|
||||
import de.tadris.fitness.osm.OsmTraceUploader;
|
||||
import de.tadris.fitness.util.DialogUtils;
|
||||
import de.tadris.fitness.util.FileUtils;
|
||||
import de.tadris.fitness.util.gpx.GpxExporter;
|
||||
import de.tadris.fitness.util.unit.UnitUtils;
|
||||
import de.tadris.fitness.view.ProgressDialogController;
|
||||
import de.westnordost.osmapi.traces.GpsTraceDetails;
|
||||
import oauth.signpost.OAuthConsumer;
|
||||
|
||||
public class ShowWorkoutActivity extends FitoTrackActivity {
|
||||
static Workout selectedWorkout;
|
||||
public class ShowWorkoutActivity extends WorkoutActivity implements DialogUtils.WorkoutDeleter {
|
||||
|
||||
List<WorkoutSample> samples;
|
||||
Workout workout;
|
||||
ViewGroup root;
|
||||
Resources.Theme theme;
|
||||
MapView map;
|
||||
TileDownloadLayer downloadLayer;
|
||||
FixedPixelCircle highlightingCircle;
|
||||
Handler mHandler= new Handler();
|
||||
TextView commentView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
workout= selectedWorkout;
|
||||
samples= Arrays.asList(Instance.getInstance(this).db.workoutDao().getAllSamplesOfWorkout(workout.id));
|
||||
setTheme(ThemeManager.getThemeByWorkout(workout));
|
||||
initBeforeContent();
|
||||
|
||||
setContentView(R.layout.activity_show_workout);
|
||||
initRoot();
|
||||
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
setTitle(WorkoutTypeCalculator.getType(workout));
|
||||
initAfterContent();
|
||||
|
||||
theme= getTheme();
|
||||
|
||||
root= findViewById(R.id.showWorkoutRoot);
|
||||
|
||||
addText(getString(R.string.comment) + ": " + workout.comment).setOnClickListener(v -> {
|
||||
TextView textView= (TextView)v;
|
||||
openEditCommentDialog(textView);
|
||||
});
|
||||
commentView = addText("", true);
|
||||
commentView.setOnClickListener(v -> openEditCommentDialog());
|
||||
updateCommentText();
|
||||
|
||||
addTitle(getString(R.string.workoutTime));
|
||||
addKeyValue(getString(R.string.workoutDate), getDate());
|
||||
@ -116,194 +80,91 @@ public class ShowWorkoutActivity extends FitoTrackActivity {
|
||||
|
||||
addKeyValue(getString(R.string.workoutDistance), UnitUtils.getDistance(workout.length), getString(R.string.workoutPace), UnitUtils.getPace(workout.avgPace));
|
||||
|
||||
addTitle(getString(R.string.workoutRoute));
|
||||
if (hasSamples()) {
|
||||
addTitle(getString(R.string.workoutRoute));
|
||||
|
||||
addMap();
|
||||
|
||||
map.setClickable(false);
|
||||
mapRoot.setOnClickListener(v -> startActivity(new Intent(ShowWorkoutActivity.this, ShowWorkoutMapActivity.class)));
|
||||
|
||||
}
|
||||
|
||||
addMap();
|
||||
|
||||
addTitle(getString(R.string.workoutSpeed));
|
||||
|
||||
addKeyValue(getString(R.string.workoutAvgSpeed), UnitUtils.getSpeed(workout.avgSpeed),
|
||||
addKeyValue(getString(R.string.workoutAvgSpeedShort), UnitUtils.getSpeed(workout.avgSpeed),
|
||||
getString(R.string.workoutTopSpeed), UnitUtils.getSpeed(workout.topSpeed));
|
||||
|
||||
addSpeedDiagram();
|
||||
if (hasSamples()) {
|
||||
|
||||
addSpeedDiagram();
|
||||
|
||||
speedDiagram.setOnClickListener(v -> startDiagramActivity(ShowWorkoutMapDiagramActivity.DIAGRAM_TYPE_SPEED));
|
||||
}
|
||||
|
||||
addTitle(getString(R.string.workoutBurnedEnergy));
|
||||
addKeyValue(getString(R.string.workoutTotalEnergy), workout.calorie + " kcal",
|
||||
getString(R.string.workoutEnergyConsumption), UnitUtils.getRelativeEnergyConsumption((double)workout.calorie / ((double)workout.length / 1000)));
|
||||
|
||||
if (hasSamples()) {
|
||||
addTitle(getString(R.string.height));
|
||||
|
||||
addKeyValue(getString(R.string.workoutAscent), UnitUtils.getDistance((int) workout.ascent),
|
||||
getString(R.string.workoutDescent), UnitUtils.getDistance((int) workout.descent));
|
||||
|
||||
addHeightDiagram();
|
||||
|
||||
heightDiagram.setOnClickListener(v -> startDiagramActivity(ShowWorkoutMapDiagramActivity.DIAGRAM_TYPE_HEIGHT));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void openEditCommentDialog(final TextView change){
|
||||
private void startDiagramActivity(String diagramType) {
|
||||
ShowWorkoutMapDiagramActivity.DIAGRAM_TYPE= diagramType;
|
||||
startActivity(new Intent(ShowWorkoutActivity.this, ShowWorkoutMapDiagramActivity.class));
|
||||
}
|
||||
|
||||
|
||||
private void openEditCommentDialog() {
|
||||
final EditText editText= new EditText(this);
|
||||
editText.setText(workout.comment);
|
||||
editText.setSingleLine(true);
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.enterComment)
|
||||
.setPositiveButton(R.string.okay, (dialog, which) -> changeComment(editText.getText().toString(), change))
|
||||
.setPositiveButton(R.string.okay, (dialog, which) -> changeComment(editText.getText().toString()))
|
||||
.setView(editText).create().show();
|
||||
}
|
||||
|
||||
void changeComment(String comment, TextView onChange){
|
||||
private void changeComment(String comment) {
|
||||
workout.comment= comment;
|
||||
Instance.getInstance(this).db.workoutDao().updateWorkout(workout);
|
||||
onChange.setText(getString(R.string.comment) + ": " + workout.comment);
|
||||
updateCommentText();
|
||||
}
|
||||
|
||||
String getDate(){
|
||||
private void updateCommentText() {
|
||||
String str = "";
|
||||
if (workout.edited) {
|
||||
str += getString(R.string.workoutEdited);
|
||||
}
|
||||
if (workout.comment != null && workout.comment.length() > 0) {
|
||||
if (str.length() > 0) {
|
||||
str += "\n";
|
||||
}
|
||||
str += getString(R.string.comment) + ": " + workout.comment;
|
||||
}
|
||||
if (str.length() == 0) {
|
||||
str = getString(R.string.noComment);
|
||||
}
|
||||
commentView.setText(str);
|
||||
}
|
||||
|
||||
private String getDate() {
|
||||
return SimpleDateFormat.getDateInstance().format(new Date(workout.start));
|
||||
}
|
||||
|
||||
|
||||
void addTitle(String title){
|
||||
TextView textView= new TextView(this);
|
||||
textView.setText(title);
|
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||
textView.setTextColor(getThemePrimaryColor());
|
||||
textView.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
textView.setAllCaps(true);
|
||||
textView.setPadding(0, 20, 0, 0);
|
||||
|
||||
root.addView(textView);
|
||||
}
|
||||
|
||||
TextView addText(String text){
|
||||
TextView textView= new TextView(this);
|
||||
textView.setText(text);
|
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||
textView.setTextColor(getThemePrimaryColor());
|
||||
textView.setPadding(0, 20, 0, 0);
|
||||
|
||||
root.addView(textView);
|
||||
|
||||
return textView;
|
||||
}
|
||||
|
||||
void addKeyValue(String key1, String value1){
|
||||
addKeyValue(key1, value1, "", "");
|
||||
}
|
||||
|
||||
void addKeyValue(String key1, String value1, String key2, String value2){
|
||||
View v= getLayoutInflater().inflate(R.layout.show_entry, root, false);
|
||||
|
||||
TextView title1= v.findViewById(R.id.v1title);
|
||||
TextView title2= v.findViewById(R.id.v2title);
|
||||
TextView v1= v.findViewById(R.id.v1value);
|
||||
TextView v2= v.findViewById(R.id.v2value);
|
||||
|
||||
title1.setText(key1);
|
||||
title2.setText(key2);
|
||||
v1.setText(value1);
|
||||
v2.setText(value2);
|
||||
|
||||
root.addView(v);
|
||||
}
|
||||
|
||||
void 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(){
|
||||
map= new MapView(this);
|
||||
downloadLayer= MapManager.setupMap(map, TileSources.Purpose.DEFAULT);
|
||||
|
||||
WorkoutLayer workoutLayer= new WorkoutLayer(samples, getThemePrimaryColor());
|
||||
map.addLayer(workoutLayer);
|
||||
|
||||
final BoundingBox bounds= new BoundingBox(workoutLayer.getLatLongs()).extendMeters(50);
|
||||
mHandler.postDelayed(() -> {
|
||||
map.getModel().mapViewPosition.setMapPosition(new MapPosition(bounds.getCenterPoint(),
|
||||
(LatLongUtils.zoomForBounds(map.getDimension(), bounds, map.getModel().displayModel.getTileSize()))));
|
||||
map.animate().alpha(1f).setDuration(1000).start();
|
||||
}, 1000);
|
||||
|
||||
map.getModel().mapViewPosition.setMapLimit(bounds);
|
||||
|
||||
|
||||
root.addView(map, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getWindowManager().getDefaultDisplay().getWidth()*3/4));
|
||||
map.setAlpha(0);
|
||||
|
||||
|
||||
Paint pGreen= AndroidGraphicFactory.INSTANCE.createPaint();
|
||||
pGreen.setColor(Color.GREEN);
|
||||
map.addLayer(new FixedPixelCircle(samples.get(0).toLatLong(), 20, pGreen, null));
|
||||
Paint pRed= AndroidGraphicFactory.INSTANCE.createPaint();
|
||||
pRed.setColor(Color.RED);
|
||||
map.addLayer(new FixedPixelCircle(samples.get(samples.size()-1).toLatLong(), 20, pRed, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
map.destroyAll();
|
||||
AndroidGraphicFactory.clearResourceMemoryCache();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause(){
|
||||
super.onPause();
|
||||
downloadLayer.onPause();
|
||||
}
|
||||
|
||||
public void onResume(){
|
||||
super.onResume();
|
||||
downloadLayer.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
@ -311,39 +172,91 @@ public class ShowWorkoutActivity extends FitoTrackActivity {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void deleteWorkout(){
|
||||
public 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, (dialog, which) -> deleteWorkout())
|
||||
.create().show();
|
||||
DialogUtils.showDeleteWorkoutDialog(this, this);
|
||||
}
|
||||
|
||||
private void exportToGpx(){
|
||||
if (!hasStoragePermission()) {
|
||||
requestStoragePermissions();
|
||||
return;
|
||||
}
|
||||
ProgressDialogController dialogController= new ProgressDialogController(this, getString(R.string.exporting));
|
||||
dialogController.setIndeterminate(true);
|
||||
dialogController.show();
|
||||
new Thread(() -> {
|
||||
try{
|
||||
String file= getFilesDir().getAbsolutePath() + "/shared/workout.gpx";
|
||||
new File(file).getParentFile().mkdirs();
|
||||
File parent = new File(file).getParentFile();
|
||||
if (!parent.exists() && !parent.mkdirs()) {
|
||||
throw new IOException("Cannot write to " + file);
|
||||
}
|
||||
Uri uri= FileProvider.getUriForFile(getBaseContext(), "de.tadris.fitness.fileprovider", new File(file));
|
||||
|
||||
GpxExporter.exportWorkout(getBaseContext(), workout, new File(file));
|
||||
dialogController.cancel();
|
||||
mHandler.post(() -> shareFile(uri));
|
||||
mHandler.post(() -> FileUtils.saveOrShareFile(this, uri, "gpx"));
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
mHandler.post(() -> showErrorDialog(e, R.string.error, R.string.errorGpxExportFailed));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private OAuthConsumer oAuthConsumer= null;
|
||||
private void prepareUpload(){
|
||||
OAuthAuthentication authentication= new OAuthAuthentication(mHandler, this, new OAuthAuthentication.OAuthAuthenticationListener() {
|
||||
@Override
|
||||
public void authenticationFailed() {
|
||||
new AlertDialog.Builder(ShowWorkoutActivity.this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.authenticationFailed)
|
||||
.setPositiveButton(R.string.okay, null)
|
||||
.create().show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticationComplete(OAuthConsumer consumer) {
|
||||
oAuthConsumer= consumer;
|
||||
showUploadOptions();
|
||||
}
|
||||
});
|
||||
|
||||
authentication.authenticateIfNecessary();
|
||||
}
|
||||
|
||||
private AlertDialog dialog = null;
|
||||
private void showUploadOptions(){
|
||||
dialog= new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.actionUploadToOSM)
|
||||
.setView(R.layout.dialog_upload_osm)
|
||||
.setPositiveButton(R.string.upload, (dialogInterface, i) -> {
|
||||
CheckBox checkBox= dialog.findViewById(R.id.uploadCutting);
|
||||
Spinner spinner= dialog.findViewById(R.id.uploadVisibility);
|
||||
EditText descriptionEdit= dialog.findViewById(R.id.uploadDescription);
|
||||
String description= descriptionEdit.getText().toString().trim();
|
||||
GpsTraceDetails.Visibility visibility;
|
||||
switch (spinner.getSelectedItemPosition()){
|
||||
case 0: visibility= GpsTraceDetails.Visibility.IDENTIFIABLE; break;
|
||||
default:
|
||||
case 1: visibility= GpsTraceDetails.Visibility.TRACKABLE; break;
|
||||
case 2: visibility= GpsTraceDetails.Visibility.PRIVATE; break;
|
||||
}
|
||||
uploadToOsm(checkBox.isChecked(), visibility, description);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void uploadToOsm(boolean cut, GpsTraceDetails.Visibility visibility, String description){
|
||||
List<WorkoutSample> samples = new ArrayList<>(this.samples);
|
||||
new OsmTraceUploader(this, mHandler, workout, samples, visibility, oAuthConsumer, cut, description).upload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
@ -355,11 +268,18 @@ public class ShowWorkoutActivity extends FitoTrackActivity {
|
||||
case R.id.actionExportGpx:
|
||||
exportToGpx();
|
||||
return true;
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
case R.id.actionUploadOSM:
|
||||
prepareUpload();
|
||||
return true;
|
||||
case R.id.actionEditComment:
|
||||
openEditCommentDialog();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
void initRoot() {
|
||||
root = findViewById(R.id.showWorkoutRoot);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.os.Bundle;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
|
||||
public class ShowWorkoutMapActivity extends WorkoutActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
initBeforeContent();
|
||||
|
||||
setContentView(R.layout.activity_show_workout_map);
|
||||
initRoot();
|
||||
|
||||
initAfterContent();
|
||||
|
||||
fullScreenItems = true;
|
||||
addMap();
|
||||
|
||||
map.setClickable(true);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void initRoot() {
|
||||
root = findViewById(R.id.showWorkoutMapParent);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.os.Bundle;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
|
||||
public class ShowWorkoutMapDiagramActivity extends WorkoutActivity {
|
||||
|
||||
public static final String DIAGRAM_TYPE_HEIGHT= "height";
|
||||
public static final String DIAGRAM_TYPE_SPEED= "speed";
|
||||
|
||||
static String DIAGRAM_TYPE;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
initBeforeContent();
|
||||
|
||||
setContentView(R.layout.activity_show_workout_map_diagram);
|
||||
initRoot();
|
||||
|
||||
initAfterContent();
|
||||
|
||||
fullScreenItems = true;
|
||||
addMap();
|
||||
map.setClickable(true);
|
||||
|
||||
diagramsInteractive= true;
|
||||
root= findViewById(R.id.showWorkoutDiagramParent);
|
||||
switch (DIAGRAM_TYPE){
|
||||
case DIAGRAM_TYPE_HEIGHT: addHeightDiagram(); break;
|
||||
case DIAGRAM_TYPE_SPEED: addSpeedDiagram(); break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void initRoot() {
|
||||
root = findViewById(R.id.showWorkoutMapParent);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.ActionBar;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.View;
|
||||
import android.widget.NumberPicker;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.util.unit.UnitUtils;
|
||||
|
||||
public class VoiceAnnouncementsSettingsActivity extends FitoTrackSettingsActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setupActionBar();
|
||||
|
||||
setTitle(R.string.voiceAnnouncementsTitle);
|
||||
|
||||
addPreferencesFromResource(R.xml.preferences_voice_announcements);
|
||||
|
||||
bindPreferenceSummaryToValue(findPreference("announcementMode"));
|
||||
|
||||
findPreference("speechConfig").setOnPreferenceClickListener(preference -> {
|
||||
showSpeechConfig();
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void showSpeechConfig() {
|
||||
UnitUtils.setUnit(this); // Maybe the user changed unit system
|
||||
|
||||
final AlertDialog.Builder d = new AlertDialog.Builder(this);
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
d.setTitle(getString(R.string.pref_voice_announcements_summary));
|
||||
View v = getLayoutInflater().inflate(R.layout.dialog_spoken_updates_picker, null);
|
||||
|
||||
NumberPicker npT = v.findViewById(R.id.spokenUpdatesTimePicker);
|
||||
npT.setMaxValue(60);
|
||||
npT.setMinValue(0);
|
||||
npT.setFormatter(value -> value == 0 ? "No speech" : value + " min");
|
||||
final String updateTimeVariable = "spokenUpdateTimePeriod";
|
||||
npT.setValue(preferences.getInt(updateTimeVariable, 0));
|
||||
npT.setWrapSelectorWheel(false);
|
||||
|
||||
final String distanceUnit = " " + UnitUtils.CHOSEN_SYSTEM.getLongDistanceUnit();
|
||||
NumberPicker npD = v.findViewById(R.id.spokenUpdatesDistancePicker);
|
||||
npD.setMaxValue(10);
|
||||
npD.setMinValue(0);
|
||||
npD.setFormatter(value -> value == 0 ? "No speech" : value + distanceUnit);
|
||||
final String updateDistanceVariable = "spokenUpdateDistancePeriod";
|
||||
npD.setValue(preferences.getInt(updateDistanceVariable, 0));
|
||||
npD.setWrapSelectorWheel(false);
|
||||
|
||||
d.setView(v);
|
||||
|
||||
d.setNegativeButton(R.string.cancel, null);
|
||||
d.setPositiveButton(R.string.okay, (dialog, which) ->
|
||||
preferences.edit()
|
||||
.putInt(updateTimeVariable, npT.getValue())
|
||||
.putInt(updateDistanceVariable, npD.getValue())
|
||||
.apply());
|
||||
|
||||
d.create().show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the {@link android.app.ActionBar}, if the API is available.
|
||||
*/
|
||||
private void setupActionBar() {
|
||||
ActionBar actionBar = getActionBar();
|
||||
if (actionBar != null) {
|
||||
// Show the Up button in the action bar.
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,334 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.os.Handler;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
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.LatLong;
|
||||
import org.mapsforge.core.model.MapPosition;
|
||||
import org.mapsforge.core.util.LatLongUtils;
|
||||
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
|
||||
import org.mapsforge.map.android.view.MapView;
|
||||
import org.mapsforge.map.layer.download.TileDownloadLayer;
|
||||
import org.mapsforge.map.layer.overlay.FixedPixelCircle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.data.WorkoutManager;
|
||||
import de.tadris.fitness.data.WorkoutSample;
|
||||
import de.tadris.fitness.map.MapManager;
|
||||
import de.tadris.fitness.map.WorkoutLayer;
|
||||
import de.tadris.fitness.util.unit.UnitUtils;
|
||||
|
||||
public abstract class WorkoutActivity extends InformationActivity {
|
||||
|
||||
public static Workout selectedWorkout;
|
||||
|
||||
List<WorkoutSample> samples;
|
||||
Workout workout;
|
||||
private Resources.Theme theme;
|
||||
MapView map;
|
||||
private TileDownloadLayer downloadLayer;
|
||||
private FixedPixelCircle highlightingCircle;
|
||||
final Handler mHandler = new Handler();
|
||||
|
||||
LineChart speedDiagram;
|
||||
LineChart heightDiagram;
|
||||
|
||||
void initBeforeContent() {
|
||||
workout= selectedWorkout;
|
||||
samples= Arrays.asList(Instance.getInstance(this).db.workoutDao().getAllSamplesOfWorkout(workout.id));
|
||||
setTheme(Instance.getInstance(this).themes.getWorkoutTypeTheme(workout.getWorkoutType()));
|
||||
}
|
||||
|
||||
void initAfterContent() {
|
||||
if (getActionBar() != null) {
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
setTitle(workout.getWorkoutType().title);
|
||||
|
||||
theme= getTheme();
|
||||
}
|
||||
|
||||
private void addDiagram(SampleConverter converter) {
|
||||
root.addView(getDiagram(converter), new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, fullScreenItems ? ViewGroup.LayoutParams.MATCH_PARENT : getWindowManager().getDefaultDisplay().getWidth()*3/4));
|
||||
}
|
||||
|
||||
boolean diagramsInteractive = false;
|
||||
|
||||
private LineChart getDiagram(SampleConverter converter) {
|
||||
LineChart chart= new LineChart(this);
|
||||
|
||||
converter.onCreate();
|
||||
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
for (WorkoutSample sample : samples) {
|
||||
// turn your data into Entry objects
|
||||
Entry e= new Entry((float)(sample.relativeTime) / 1000f / 60f, converter.getValue(sample));
|
||||
entries.add(e);
|
||||
converter.sampleGetsEntry(sample, e);
|
||||
}
|
||||
|
||||
LineDataSet dataSet = new LineDataSet(entries, converter.getName()); // add entries to dataset
|
||||
dataSet.setColor(getThemePrimaryColor());
|
||||
dataSet.setValueTextColor(getThemePrimaryColor());
|
||||
dataSet.setDrawCircles(false);
|
||||
dataSet.setLineWidth(4);
|
||||
dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
|
||||
|
||||
Description description= new Description();
|
||||
description.setText(converter.getDescription());
|
||||
|
||||
LineData lineData = new LineData(dataSet);
|
||||
chart.setData(lineData);
|
||||
chart.setScaleXEnabled(diagramsInteractive);
|
||||
chart.setScaleYEnabled(false);
|
||||
chart.setDescription(description);
|
||||
if(diagramsInteractive){
|
||||
chart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
|
||||
@Override
|
||||
public void onValueSelected(Entry e, Highlight h) {
|
||||
onNothingSelected();
|
||||
onDiagramValueSelected(findSample(converter, e).toLatLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected() {
|
||||
if(highlightingCircle != null){
|
||||
map.getLayerManager().getLayers().remove(highlightingCircle);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
chart.invalidate();
|
||||
|
||||
converter.afterAdd(chart);
|
||||
|
||||
return chart;
|
||||
}
|
||||
|
||||
private void onDiagramValueSelected(LatLong latLong) {
|
||||
Paint p= AndroidGraphicFactory.INSTANCE.createPaint();
|
||||
p.setColor(0xff693cff);
|
||||
highlightingCircle= new FixedPixelCircle(latLong, 20, p, null);
|
||||
map.addLayer(highlightingCircle);
|
||||
|
||||
if(!map.getBoundingBox().contains(latLong)){
|
||||
map.getModel().mapViewPosition.animateTo(latLong);
|
||||
}
|
||||
}
|
||||
|
||||
interface SampleConverter{
|
||||
void onCreate();
|
||||
float getValue(WorkoutSample sample);
|
||||
void sampleGetsEntry(WorkoutSample sample, Entry entry);
|
||||
String getName();
|
||||
String getDescription();
|
||||
boolean compare(WorkoutSample sample, Entry entry);
|
||||
void afterAdd(LineChart chart);
|
||||
}
|
||||
|
||||
private WorkoutSample findSample(SampleConverter converter, Entry entry) {
|
||||
for(WorkoutSample sample : samples){
|
||||
if(converter.compare(sample, entry)){
|
||||
return sample;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void addHeightDiagram(){
|
||||
addDiagram(new SampleConverter() {
|
||||
@Override
|
||||
public void onCreate() { }
|
||||
|
||||
@Override
|
||||
public float getValue(WorkoutSample sample) {
|
||||
return (float) UnitUtils.CHOSEN_SYSTEM.getDistanceFromMeters(sample.elevation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sampleGetsEntry(WorkoutSample sample, Entry entry) {
|
||||
sample.tmpHeightEntry= entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getString(R.string.height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "min - " + UnitUtils.CHOSEN_SYSTEM.getShortDistanceUnit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean compare(WorkoutSample sample, Entry entry) {
|
||||
return sample.tmpHeightEntry.equalTo(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterAdd(LineChart chart) {
|
||||
heightDiagram= chart;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void addSpeedDiagram(){
|
||||
addDiagram(new SampleConverter() {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
WorkoutManager.roundSpeedValues(samples);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getValue(WorkoutSample sample) {
|
||||
return (float)UnitUtils.CHOSEN_SYSTEM.getSpeedFromMeterPerSecond(sample.tmpRoundedSpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sampleGetsEntry(WorkoutSample sample, Entry entry) {
|
||||
sample.tmpSpeedEntry= entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getString(R.string.workoutSpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "min - " + UnitUtils.CHOSEN_SYSTEM.getSpeedUnit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean compare(WorkoutSample sample, Entry entry) {
|
||||
return sample.tmpSpeedEntry.equalTo(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterAdd(LineChart chart) {
|
||||
speedDiagram= chart;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
boolean fullScreenItems = false;
|
||||
LinearLayout mapRoot;
|
||||
|
||||
void addMap(){
|
||||
map= new MapView(this);
|
||||
downloadLayer = MapManager.setupMap(map);
|
||||
|
||||
WorkoutLayer workoutLayer= new WorkoutLayer(samples, getThemePrimaryColor());
|
||||
map.addLayer(workoutLayer);
|
||||
|
||||
final BoundingBox bounds= new BoundingBox(workoutLayer.getLatLongs()).extendMeters(50);
|
||||
mHandler.postDelayed(() -> {
|
||||
map.getModel().mapViewPosition.setMapPosition(new MapPosition(bounds.getCenterPoint(),
|
||||
(LatLongUtils.zoomForBounds(map.getDimension(), bounds, map.getModel().displayModel.getTileSize()))));
|
||||
map.animate().alpha(1f).setDuration(1000).start();
|
||||
}, 1000);
|
||||
|
||||
map.getModel().mapViewPosition.setMapLimit(bounds);
|
||||
|
||||
mapRoot= new LinearLayout(this);
|
||||
mapRoot.setOrientation(LinearLayout.VERTICAL);
|
||||
mapRoot.addView(map);
|
||||
|
||||
root.addView(mapRoot, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, fullScreenItems ? ViewGroup.LayoutParams.MATCH_PARENT : getMapHeight()));
|
||||
map.setAlpha(0);
|
||||
|
||||
|
||||
Paint pGreen= AndroidGraphicFactory.INSTANCE.createPaint();
|
||||
pGreen.setColor(Color.GREEN);
|
||||
map.addLayer(new FixedPixelCircle(samples.get(0).toLatLong(), 20, pGreen, null));
|
||||
Paint pRed= AndroidGraphicFactory.INSTANCE.createPaint();
|
||||
pRed.setColor(Color.RED);
|
||||
|
||||
map.addLayer(new FixedPixelCircle(samples.get(samples.size()-1).toLatLong(), 20, pRed, null));
|
||||
|
||||
map.setClickable(false);
|
||||
|
||||
}
|
||||
|
||||
private int getMapHeight() {
|
||||
return getWindowManager().getDefaultDisplay().getWidth()*3/4;
|
||||
}
|
||||
|
||||
protected boolean hasSamples() {
|
||||
return samples.size() > 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (map != null) {
|
||||
map.destroyAll();
|
||||
}
|
||||
AndroidGraphicFactory.clearResourceMemoryCache();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause(){
|
||||
super.onPause();
|
||||
if (downloadLayer != null) {
|
||||
downloadLayer.onPause();
|
||||
}
|
||||
}
|
||||
|
||||
public void onResume(){
|
||||
super.onResume();
|
||||
if (downloadLayer != null) {
|
||||
downloadLayer.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -22,7 +22,7 @@ package de.tadris.fitness.data;
|
||||
import androidx.room.Database;
|
||||
import androidx.room.RoomDatabase;
|
||||
|
||||
@Database(version = 1, entities = {Workout.class, WorkoutSample.class})
|
||||
@Database(version = 3, entities = {Workout.class, WorkoutSample.class})
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
public abstract WorkoutDao workoutDao();
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -25,7 +25,7 @@ import android.preference.PreferenceManager;
|
||||
|
||||
public class UserPreferences {
|
||||
|
||||
private SharedPreferences preferences;
|
||||
private final SharedPreferences preferences;
|
||||
|
||||
public UserPreferences(Context context) {
|
||||
this.preferences= PreferenceManager.getDefaultSharedPreferences(context);
|
||||
@ -35,4 +35,16 @@ public class UserPreferences {
|
||||
return preferences.getInt("weight", 80);
|
||||
}
|
||||
|
||||
public int getSpokenUpdateTimePeriod(){
|
||||
return preferences.getInt("spokenUpdateTimePeriod", 0);
|
||||
}
|
||||
|
||||
public int getSpokenUpdateDistancePeriod(){
|
||||
return preferences.getInt("spokenUpdateDistancePeriod", 0);
|
||||
}
|
||||
|
||||
public String getMapStyle(){
|
||||
return preferences.getString("mapStyle", "osm.mapnik");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -19,19 +19,21 @@
|
||||
|
||||
package de.tadris.fitness.data;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
@Entity(tableName = "workout")
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Workout{
|
||||
|
||||
public static final String WORKOUT_TYPE_RUNNING= "running";
|
||||
public static final String WORKOUT_TYPE_HIKING= "hiking";
|
||||
public static final String WORKOUT_TYPE_CYCLING= "cycling";
|
||||
|
||||
@PrimaryKey
|
||||
public long id;
|
||||
|
||||
@ -60,21 +62,44 @@ public class Workout{
|
||||
public double topSpeed;
|
||||
|
||||
/**
|
||||
* Average pace of workout in km/min
|
||||
* Average pace of workout in min/km
|
||||
*/
|
||||
public double avgPace;
|
||||
|
||||
public String workoutType;
|
||||
@ColumnInfo(name = "workoutType")
|
||||
@JsonProperty(value = "workoutType")
|
||||
public String workoutTypeId;
|
||||
|
||||
public float ascent;
|
||||
|
||||
public float descent;
|
||||
|
||||
public int calorie;
|
||||
|
||||
public boolean edited;
|
||||
|
||||
public String toString(){
|
||||
if(comment.length() > 2){
|
||||
return comment;
|
||||
}else{
|
||||
return SimpleDateFormat.getDateTimeInstance().format(new Date(start));
|
||||
return getDateString();
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getDateString(){
|
||||
return SimpleDateFormat.getDateTimeInstance().format(new Date(start));
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public WorkoutType getWorkoutType() {
|
||||
return WorkoutType.getTypeById(workoutTypeId);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setWorkoutType(WorkoutType workoutType) {
|
||||
this.workoutTypeId = workoutType.id;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
122
app/src/main/java/de/tadris/fitness/data/WorkoutBuilder.java
Normal file
122
app/src/main/java/de/tadris/fitness/data/WorkoutBuilder.java
Normal file
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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 android.content.Context;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.util.CalorieCalculator;
|
||||
|
||||
public class WorkoutBuilder {
|
||||
|
||||
private WorkoutType workoutType;
|
||||
|
||||
private Calendar start;
|
||||
private long duration;
|
||||
|
||||
private int length;
|
||||
|
||||
private String comment;
|
||||
|
||||
public WorkoutBuilder() {
|
||||
workoutType = WorkoutType.RUNNING;
|
||||
start = Calendar.getInstance();
|
||||
duration = 1000L * 60 * 10;
|
||||
length = 500;
|
||||
comment = "";
|
||||
}
|
||||
|
||||
public Workout create(Context context) {
|
||||
Workout workout = new Workout();
|
||||
|
||||
// Calculate values
|
||||
workout.start = start.getTimeInMillis();
|
||||
workout.duration = duration;
|
||||
workout.end = workout.start + workout.duration;
|
||||
|
||||
workout.id = workout.start;
|
||||
workout.setWorkoutType(workoutType);
|
||||
|
||||
workout.length = length;
|
||||
|
||||
workout.avgSpeed = (double) length / (double) (duration / 1000);
|
||||
workout.topSpeed = 0;
|
||||
workout.avgPace = ((double) workout.duration / 1000 / 60) / ((double) workout.length / 1000);
|
||||
|
||||
workout.pauseDuration = 0;
|
||||
workout.ascent = 0;
|
||||
workout.descent = 0;
|
||||
|
||||
workout.calorie = CalorieCalculator.calculateCalories(workout, Instance.getInstance(context).userPreferences.getUserWeight());
|
||||
workout.comment = comment;
|
||||
|
||||
workout.edited = true;
|
||||
|
||||
return workout;
|
||||
}
|
||||
|
||||
public Workout insertWorkout(Context context) {
|
||||
Workout workout = create(context);
|
||||
Instance.getInstance(context).db.workoutDao().insertWorkout(workout);
|
||||
return workout;
|
||||
}
|
||||
|
||||
public WorkoutType getWorkoutType() {
|
||||
return workoutType;
|
||||
}
|
||||
|
||||
public void setWorkoutType(WorkoutType workoutType) {
|
||||
this.workoutType = workoutType;
|
||||
}
|
||||
|
||||
public Calendar getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(Calendar start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void setLength(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -19,65 +19,10 @@
|
||||
|
||||
package de.tadris.fitness.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.util.CalorieCalculator;
|
||||
|
||||
public class WorkoutManager {
|
||||
|
||||
public static void insertWorkout(Context context, Workout workout, List<WorkoutSample> samples){
|
||||
AppDatabase db= Instance.getInstance(context).db;
|
||||
|
||||
|
||||
workout.id= System.currentTimeMillis();
|
||||
|
||||
// Delete Samples with same time
|
||||
for(int i= samples.size()-2; i >= 0; i--){
|
||||
WorkoutSample sample= samples.get(i);
|
||||
WorkoutSample lastSample= samples.get(i+1);
|
||||
if(sample.absoluteTime == lastSample.absoluteTime){
|
||||
samples.remove(lastSample);
|
||||
Log.i("WorkoutManager", "Removed samples at " + sample.absoluteTime + " rel: " + sample.relativeTime + "; " + lastSample.relativeTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculating values
|
||||
double length= 0;
|
||||
for(int i= 1; i < samples.size(); i++){
|
||||
double sampleLength= samples.get(i - 1).toLatLong().sphericalDistance(samples.get(i).toLatLong());
|
||||
long timeDiff= (samples.get(i).relativeTime - samples.get(i - 1).relativeTime) / 1000;
|
||||
length+= sampleLength;
|
||||
samples.get(i).speed= Math.abs(sampleLength / timeDiff);
|
||||
}
|
||||
workout.length= (int)length;
|
||||
workout.avgSpeed= ((double) workout.length) / ((double) workout.duration / 1000);
|
||||
workout.avgPace= ((double)workout.duration / 1000 / 60) / ((double) workout.length / 1000);
|
||||
workout.calorie= CalorieCalculator.calculateCalories(workout, Instance.getInstance(context).userPreferences.getUserWeight());
|
||||
|
||||
// Setting workoutId in the samples
|
||||
int i= 0;
|
||||
double topSpeed= 0;
|
||||
for(WorkoutSample sample : samples){
|
||||
i++;
|
||||
sample.id= workout.id + i;
|
||||
sample.workoutId= workout.id;
|
||||
if(sample.speed > topSpeed){
|
||||
topSpeed= sample.speed;
|
||||
}
|
||||
}
|
||||
|
||||
workout.topSpeed= topSpeed;
|
||||
|
||||
|
||||
// Saving workout and samples
|
||||
db.workoutDao().insertWorkoutAndSamples(workout, samples.toArray(new WorkoutSample[0]));
|
||||
|
||||
}
|
||||
|
||||
public static void roundSpeedValues(List<WorkoutSample> samples){
|
||||
for(int i= 0; i < samples.size(); i++){
|
||||
WorkoutSample sample= samples.get(i);
|
||||
@ -91,4 +36,15 @@ public class WorkoutManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void calculateInclination(List<WorkoutSample> samples){
|
||||
samples.get(0).tmpInclination= 0;
|
||||
for(int i= 1; i < samples.size(); i++){
|
||||
WorkoutSample sample= samples.get(i);
|
||||
WorkoutSample lastSample= samples.get(i);
|
||||
double elevationDifference= sample.elevation - sample.elevation;
|
||||
double distance= sample.toLatLong().sphericalDistance(lastSample.toLatLong());
|
||||
sample.tmpInclination= (float)(elevationDifference*100/distance);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ import androidx.room.Ignore;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.github.mikephil.charting.data.Entry;
|
||||
|
||||
import org.mapsforge.core.model.LatLong;
|
||||
@ -38,6 +39,7 @@ import static androidx.room.ForeignKey.CASCADE;
|
||||
parentColumns = "id",
|
||||
childColumns = "workout_id",
|
||||
onDelete = CASCADE))
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class WorkoutSample{
|
||||
|
||||
@PrimaryKey
|
||||
@ -56,18 +58,32 @@ public class WorkoutSample{
|
||||
|
||||
public double elevation;
|
||||
|
||||
public double relativeElevation;
|
||||
|
||||
public double speed;
|
||||
|
||||
@JsonIgnore
|
||||
@Ignore
|
||||
public Entry tmpEntry;
|
||||
public Entry tmpHeightEntry;
|
||||
|
||||
@JsonIgnore
|
||||
@Ignore
|
||||
public Entry tmpSpeedEntry;
|
||||
|
||||
@JsonIgnore
|
||||
@Ignore
|
||||
public double tmpRoundedSpeed;
|
||||
|
||||
@JsonIgnore
|
||||
@Ignore
|
||||
public double tmpElevation;
|
||||
|
||||
@JsonIgnore
|
||||
@Ignore
|
||||
public float tmpPressure;
|
||||
|
||||
@JsonIgnore
|
||||
@Ignore
|
||||
public float tmpInclination;
|
||||
|
||||
public LatLong toLatLong(){
|
||||
return new LatLong(lat, lon);
|
||||
}
|
||||
|
||||
59
app/src/main/java/de/tadris/fitness/data/WorkoutType.java
Normal file
59
app/src/main/java/de/tadris/fitness/data/WorkoutType.java
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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 androidx.annotation.StringRes;
|
||||
import androidx.annotation.StyleRes;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
|
||||
public enum WorkoutType {
|
||||
|
||||
RUNNING("running", R.string.workoutTypeRunning, 7, true, R.style.Running, R.style.RunningDark),
|
||||
HIKING("hiking", R.string.workoutTypeHiking, 7, true, R.style.Hiking, R.style.HikingDark),
|
||||
CYCLING("cycling", R.string.workoutTypeCycling, 12, true, R.style.Bicycling, R.style.BicyclingDark),
|
||||
OTHER("other", R.string.workoutTypeOther, 7, true, R.style.AppTheme, R.style.AppThemeDark);
|
||||
|
||||
public String id;
|
||||
@StringRes
|
||||
public int title;
|
||||
public int minDistance; // Minimum distance between samples
|
||||
public boolean hasGPS;
|
||||
@StyleRes
|
||||
public int lightTheme, darkTheme;
|
||||
|
||||
WorkoutType(String id, int title, int minDistance, boolean hasGPS, int lightTheme, int darkTheme) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.minDistance = minDistance;
|
||||
this.hasGPS = hasGPS;
|
||||
this.lightTheme = lightTheme;
|
||||
this.darkTheme = darkTheme;
|
||||
}
|
||||
|
||||
public static WorkoutType getTypeById(String id) {
|
||||
for (WorkoutType type : values()) {
|
||||
if (type.id.equals(id)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return OTHER;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.dialog;
|
||||
|
||||
import android.app.DatePickerDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.os.Bundle;
|
||||
import android.widget.DatePicker;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
public class DatePickerFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener {
|
||||
|
||||
public DatePickerCallback callback;
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
// Use the current date as the default date in the picker
|
||||
final Calendar c = Calendar.getInstance();
|
||||
c.setTimeInMillis(System.currentTimeMillis());
|
||||
int year = c.get(Calendar.YEAR);
|
||||
int month = c.get(Calendar.MONTH);
|
||||
int day = c.get(Calendar.DAY_OF_MONTH);
|
||||
|
||||
// Create a new instance of DatePickerDialog and return it
|
||||
return new DatePickerDialog(getActivity(), this, year, month, day);
|
||||
}
|
||||
|
||||
public void onDateSet(DatePicker view, int year, int month, int day) {
|
||||
callback.onDatePick(year, month, day);
|
||||
}
|
||||
|
||||
public interface DatePickerCallback {
|
||||
void onDatePick(int year, int month, int day);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.dialog;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.view.View;
|
||||
import android.widget.NumberPicker;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
|
||||
public class DurationPickerDialogFragment {
|
||||
|
||||
public Activity context;
|
||||
public DurationPickListener listener;
|
||||
public long initialDuration;
|
||||
|
||||
public DurationPickerDialogFragment(Activity context, DurationPickListener listener, long initialDuration) {
|
||||
this.context = context;
|
||||
this.listener = listener;
|
||||
this.initialDuration = initialDuration;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
final AlertDialog.Builder d = new AlertDialog.Builder(context);
|
||||
d.setTitle(R.string.setDuration);
|
||||
View v = context.getLayoutInflater().inflate(R.layout.dialog_duration_picker, null);
|
||||
NumberPicker hours = v.findViewById(R.id.durationPickerHours);
|
||||
hours.setFormatter(value -> value + " " + context.getString(R.string.timeHourShort));
|
||||
hours.setMinValue(0);
|
||||
hours.setMaxValue(24);
|
||||
hours.setValue(getInitialHours());
|
||||
|
||||
NumberPicker minutes = v.findViewById(R.id.durationPickerMinutes);
|
||||
minutes.setFormatter(value -> value + " " + context.getString(R.string.timeMinuteShort));
|
||||
minutes.setMinValue(0);
|
||||
minutes.setMaxValue(60);
|
||||
minutes.setValue(getInitialMinutes());
|
||||
|
||||
NumberPicker seconds = v.findViewById(R.id.durationPickerSeconds);
|
||||
seconds.setFormatter(value -> value + " " + context.getString(R.string.timeSecondsShort));
|
||||
seconds.setMinValue(0);
|
||||
seconds.setMaxValue(60);
|
||||
seconds.setValue(getInitialSeconds());
|
||||
|
||||
d.setView(v);
|
||||
|
||||
d.setNegativeButton(R.string.cancel, null);
|
||||
d.setPositiveButton(R.string.okay, (dialog, which) -> {
|
||||
listener.onDurationPick(getMillisFromPick(hours.getValue(), minutes.getValue(), seconds.getValue()));
|
||||
});
|
||||
|
||||
d.create().show();
|
||||
}
|
||||
|
||||
private int getInitialHours() {
|
||||
return (int) (initialDuration / 1000 / 60 / 60);
|
||||
}
|
||||
|
||||
private int getInitialMinutes() {
|
||||
return (int) (initialDuration / 1000 / 60 % 60);
|
||||
}
|
||||
|
||||
private int getInitialSeconds() {
|
||||
return (int) (initialDuration / 1000 % 60);
|
||||
}
|
||||
|
||||
private static long getMillisFromPick(int hours, int minutes, int seconds) {
|
||||
long secondInMillis = 1000L;
|
||||
long minuteInMillis = secondInMillis * 60;
|
||||
long hourInMillis = minuteInMillis * 60;
|
||||
return hours * hourInMillis + minutes * minuteInMillis + seconds * secondInMillis;
|
||||
}
|
||||
|
||||
public interface DurationPickListener {
|
||||
void onDurationPick(long duration);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.dialog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.data.WorkoutType;
|
||||
|
||||
public class SelectWorkoutTypeDialog {
|
||||
|
||||
private Activity context;
|
||||
private WorkoutTypeSelectListener listener;
|
||||
private WorkoutType[] options;
|
||||
|
||||
public SelectWorkoutTypeDialog(Activity context, WorkoutTypeSelectListener listener) {
|
||||
this.context = context;
|
||||
this.listener = listener;
|
||||
this.options = WorkoutType.values();
|
||||
}
|
||||
|
||||
public void show() {
|
||||
AlertDialog.Builder builderSingle = new AlertDialog.Builder(context);
|
||||
|
||||
final ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(context, R.layout.select_dialog_singlechoice_material);
|
||||
for (WorkoutType type : options) {
|
||||
arrayAdapter.add(context.getString(type.title));
|
||||
}
|
||||
|
||||
builderSingle.setAdapter(arrayAdapter, (dialog, which) -> listener.onSelectWorkoutType(options[which]));
|
||||
builderSingle.show();
|
||||
}
|
||||
|
||||
public interface WorkoutTypeSelectListener {
|
||||
void onSelectWorkoutType(WorkoutType workoutType);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.app.TimePickerDialog;
|
||||
import android.os.Bundle;
|
||||
import android.text.format.DateFormat;
|
||||
import android.widget.TimePicker;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
public class TimePickerFragment extends DialogFragment implements TimePickerDialog.OnTimeSetListener {
|
||||
|
||||
public TimePickerCallback callback;
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
// Use the current time as the default values for the picker
|
||||
final Calendar c = Calendar.getInstance();
|
||||
int hour = c.get(Calendar.HOUR_OF_DAY);
|
||||
int minute = c.get(Calendar.MINUTE);
|
||||
|
||||
// Create a new instance of TimePickerDialog and return it
|
||||
return new TimePickerDialog(getActivity(), this, hour, minute, DateFormat.is24HourFormat(getActivity()));
|
||||
}
|
||||
|
||||
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
|
||||
callback.onTimePick(hourOfDay, minute);
|
||||
}
|
||||
|
||||
public interface TimePickerCallback {
|
||||
void onTimePick(int hour, int minute);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.export;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.data.AppDatabase;
|
||||
import de.tadris.fitness.util.unit.UnitUtils;
|
||||
|
||||
public class BackupController {
|
||||
|
||||
private static final int VERSION = 1;
|
||||
|
||||
private final Context context;
|
||||
private final File output;
|
||||
private final ExportStatusListener listener;
|
||||
private AppDatabase database;
|
||||
|
||||
private FitoTrackDataContainer dataContainer;
|
||||
|
||||
public BackupController(Context context, File output, ExportStatusListener listener) {
|
||||
this.context = context;
|
||||
this.output = output;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void exportData() throws IOException {
|
||||
listener.onStatusChanged(0, context.getString(R.string.initialising));
|
||||
init();
|
||||
listener.onStatusChanged(20, context.getString(R.string.workouts));
|
||||
saveWorkoutsToContainer();
|
||||
listener.onStatusChanged(40, context.getString(R.string.locationData));
|
||||
saveSamplesToContainer();
|
||||
listener.onStatusChanged(60, context.getString(R.string.converting));
|
||||
writeContainerToOutputFile();
|
||||
listener.onStatusChanged(100, context.getString(R.string.finished));
|
||||
}
|
||||
|
||||
private void init(){
|
||||
database= Instance.getInstance(context).db;
|
||||
UnitUtils.setUnit(context); // Ensure unit system is correct
|
||||
newContainer();
|
||||
}
|
||||
|
||||
private void newContainer(){
|
||||
dataContainer= new FitoTrackDataContainer();
|
||||
dataContainer.setVersion(VERSION);
|
||||
dataContainer.setWorkouts(new ArrayList<>());
|
||||
dataContainer.setSamples(new ArrayList<>());
|
||||
}
|
||||
|
||||
private void saveWorkoutsToContainer(){
|
||||
dataContainer.getWorkouts().addAll(Arrays.asList(database.workoutDao().getWorkouts()));
|
||||
}
|
||||
|
||||
private void saveSamplesToContainer(){
|
||||
dataContainer.getSamples().addAll(Arrays.asList(database.workoutDao().getSamples()));
|
||||
}
|
||||
|
||||
private void writeContainerToOutputFile() throws IOException {
|
||||
XmlMapper mapper= new XmlMapper();
|
||||
mapper.writeValue(output, dataContainer);
|
||||
}
|
||||
|
||||
public interface ExportStatusListener{
|
||||
void onStatusChanged(int progress, String action);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.export;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.data.WorkoutSample;
|
||||
|
||||
@JacksonXmlRootElement(localName = "fito-track")
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class FitoTrackDataContainer {
|
||||
|
||||
private int version;
|
||||
private List<Workout> workouts;
|
||||
private List<WorkoutSample> samples;
|
||||
|
||||
public FitoTrackDataContainer(){}
|
||||
|
||||
public FitoTrackDataContainer(int version, List<Workout> workouts, List<WorkoutSample> samples) {
|
||||
this.version = version;
|
||||
this.workouts = workouts;
|
||||
this.samples = samples;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public List<Workout> getWorkouts() {
|
||||
return workouts;
|
||||
}
|
||||
|
||||
public void setWorkouts(List<Workout> workouts) {
|
||||
this.workouts = workouts;
|
||||
}
|
||||
|
||||
public List<WorkoutSample> getSamples() {
|
||||
return samples;
|
||||
}
|
||||
|
||||
public void setSamples(List<WorkoutSample> samples) {
|
||||
this.samples = samples;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.export;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.data.AppDatabase;
|
||||
import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.data.WorkoutSample;
|
||||
|
||||
public class RestoreController {
|
||||
|
||||
private final Context context;
|
||||
private final Uri input;
|
||||
private final ImportStatusListener listener;
|
||||
private FitoTrackDataContainer dataContainer;
|
||||
private final AppDatabase database;
|
||||
|
||||
public RestoreController(Context context, Uri input, ImportStatusListener listener) {
|
||||
this.context = context;
|
||||
this.input = input;
|
||||
this.listener = listener;
|
||||
this.database= Instance.getInstance(context).db;
|
||||
}
|
||||
|
||||
public void restoreData() throws IOException, UnsupportedVersionException{
|
||||
listener.onStatusChanged(0, context.getString(R.string.loadingFile));
|
||||
loadDataFromFile();
|
||||
checkVersion();
|
||||
restoreDatabase();
|
||||
listener.onStatusChanged(100, context.getString(R.string.finished));
|
||||
}
|
||||
|
||||
private void loadDataFromFile() throws IOException {
|
||||
XmlMapper xmlMapper = new XmlMapper();
|
||||
xmlMapper.configure(JsonParser.Feature.IGNORE_UNDEFINED, true);
|
||||
dataContainer = xmlMapper.readValue(context.getContentResolver().openInputStream(input), FitoTrackDataContainer.class);
|
||||
}
|
||||
|
||||
private void checkVersion() throws UnsupportedVersionException {
|
||||
if (dataContainer.getVersion() != 1) {
|
||||
throw new UnsupportedVersionException("Version Code" + dataContainer.getVersion() + " is unsupported!");
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreDatabase(){
|
||||
database.runInTransaction(() -> {
|
||||
resetDatabase();
|
||||
restoreWorkouts();
|
||||
restoreSamples();
|
||||
});
|
||||
}
|
||||
|
||||
private void resetDatabase(){
|
||||
database.clearAllTables();
|
||||
}
|
||||
|
||||
private void restoreWorkouts(){
|
||||
listener.onStatusChanged(60, context.getString(R.string.workouts));
|
||||
if (dataContainer.getWorkouts() != null) {
|
||||
for (Workout workout : dataContainer.getWorkouts()) {
|
||||
database.workoutDao().insertWorkout(workout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreSamples(){
|
||||
listener.onStatusChanged(80, context.getString(R.string.locationData));
|
||||
if (dataContainer.getSamples() != null) {
|
||||
for (WorkoutSample sample : dataContainer.getSamples()) {
|
||||
database.workoutDao().insertSample(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface ImportStatusListener{
|
||||
void onStatusChanged(int progress, String action);
|
||||
}
|
||||
|
||||
static class UnsupportedVersionException extends Exception {
|
||||
UnsupportedVersionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,98 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -19,28 +19,31 @@
|
||||
|
||||
package de.tadris.fitness.map;
|
||||
|
||||
import org.mapsforge.core.model.LatLong;
|
||||
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
|
||||
import org.mapsforge.map.android.util.AndroidUtil;
|
||||
import org.mapsforge.map.android.view.MapView;
|
||||
import org.mapsforge.map.layer.cache.TileCache;
|
||||
import org.mapsforge.map.layer.download.TileDownloadLayer;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.map.tilesource.FitoTrackTileSource;
|
||||
import de.tadris.fitness.map.tilesource.HumanitarianTileSource;
|
||||
import de.tadris.fitness.map.tilesource.MapnikTileSource;
|
||||
import de.tadris.fitness.map.tilesource.TileSources;
|
||||
import de.tadris.fitness.map.tilesource.ThunderforestTileSource;
|
||||
|
||||
public class MapManager {
|
||||
|
||||
public static TileDownloadLayer setupMap(MapView mapView, TileSources.Purpose purpose){
|
||||
public static TileDownloadLayer setupMap(MapView mapView) {
|
||||
FitoTrackTileSource tileSource;
|
||||
switch (purpose){
|
||||
/*case OUTDOOR: tileSource= ThunderforestTileSource.OUTDOORS; break;
|
||||
case CYCLING: tileSource= ThunderforestTileSource.CYLE_MAP; break;*/
|
||||
|
||||
case DEFAULT:
|
||||
default:
|
||||
tileSource= MapnikTileSource.INSTANCE; break;
|
||||
String chosenTileLayer= Instance.getInstance(mapView.getContext()).userPreferences.getMapStyle();
|
||||
switch (chosenTileLayer){
|
||||
case "osm.humanitarian": tileSource= HumanitarianTileSource.INSTANCE; break;
|
||||
case "thunderforest.outdoors": tileSource= ThunderforestTileSource.OUTDOORS; break;
|
||||
case "thunderforest.cycle":
|
||||
tileSource = ThunderforestTileSource.CYCLE_MAP;
|
||||
break;
|
||||
default: tileSource= MapnikTileSource.INSTANCE; break; // Inclusive "osm.mapnik"
|
||||
}
|
||||
tileSource.setUserAgent("mapsforge-android");
|
||||
|
||||
@ -59,7 +62,6 @@ public class MapManager {
|
||||
mapView.getLayerManager().redrawLayers();
|
||||
|
||||
mapView.setZoomLevel((byte) 18);
|
||||
mapView.setCenter(new LatLong(52, 13));
|
||||
|
||||
return downloadLayer;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -30,7 +30,7 @@ import de.tadris.fitness.data.WorkoutSample;
|
||||
|
||||
public class WorkoutLayer extends Polyline {
|
||||
|
||||
public static Paint getDEFAULT_PAINT_STROKE(int color){
|
||||
private static Paint getDEFAULT_PAINT_STROKE(int color) {
|
||||
Paint paint= AndroidGraphicFactory.INSTANCE.createPaint();
|
||||
paint.setStyle(Style.STROKE);
|
||||
paint.setColor(color);
|
||||
@ -38,13 +38,13 @@ public class WorkoutLayer extends Polyline {
|
||||
return paint;
|
||||
}
|
||||
|
||||
List<WorkoutSample> samples;
|
||||
private final List<WorkoutSample> samples;
|
||||
|
||||
public WorkoutLayer(List<WorkoutSample> samples, int color) {
|
||||
this(getDEFAULT_PAINT_STROKE(color), samples);
|
||||
}
|
||||
|
||||
public WorkoutLayer(Paint paintStroke, List<WorkoutSample> samples) {
|
||||
private WorkoutLayer(Paint paintStroke, List<WorkoutSample> samples) {
|
||||
super(paintStroke, AndroidGraphicFactory.INSTANCE);
|
||||
this.samples = samples;
|
||||
init();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -23,7 +23,7 @@ import org.mapsforge.map.layer.download.tilesource.AbstractTileSource;
|
||||
|
||||
public abstract class FitoTrackTileSource extends AbstractTileSource {
|
||||
|
||||
public FitoTrackTileSource(String[] hostNames, int port) {
|
||||
FitoTrackTileSource(String[] hostNames, int port) {
|
||||
super(hostNames, port);
|
||||
defaultTimeToLive = 8279000;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -26,7 +26,7 @@ import java.net.URL;
|
||||
|
||||
public class HumanitarianTileSource extends FitoTrackTileSource {
|
||||
|
||||
public static HumanitarianTileSource INSTANCE= new HumanitarianTileSource(new String[]{"tile-a.openstreetmap.fr", "tile-b.openstreetmap.fr", "tile-c.openstreetmap.fr"}, 443);
|
||||
public static final 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";
|
||||
@ -34,7 +34,7 @@ public class HumanitarianTileSource extends FitoTrackTileSource {
|
||||
private static final int ZOOM_LEVEL_MIN = 0;
|
||||
private static final String NAME = "Humanitarian";
|
||||
|
||||
public HumanitarianTileSource(String[] hostNames, int port) {
|
||||
private HumanitarianTileSource(String[] hostNames, int port) {
|
||||
super(hostNames, port);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -30,11 +30,11 @@ public class MapnikTileSource extends FitoTrackTileSource {
|
||||
"a.tile.openstreetmap.org", "b.tile.openstreetmap.org", "c.tile.openstreetmap.org"}, 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_MAX = 19;
|
||||
private static final int ZOOM_LEVEL_MIN = 0;
|
||||
private static final String NAME = "OSM Mapnik";
|
||||
|
||||
public MapnikTileSource(String[] hostNames, int port) {
|
||||
private MapnikTileSource(String[] hostNames, int port) {
|
||||
super(hostNames, port);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -26,19 +26,19 @@ import java.net.URL;
|
||||
|
||||
public class ThunderforestTileSource extends FitoTrackTileSource{
|
||||
|
||||
public static final String API_KEY= "87b07337e42c405db6d8d39b1c0c179e";
|
||||
private static final String API_KEY = "87b07337e42c405db6d8d39b1c0c179e";
|
||||
|
||||
public static final ThunderforestTileSource OUTDOORS = new ThunderforestTileSource("outdoors", "Outdoor");
|
||||
public static final ThunderforestTileSource CYLE_MAP = new ThunderforestTileSource("cycle", "Cycle Map");
|
||||
public static final ThunderforestTileSource CYCLE_MAP = new ThunderforestTileSource("cycle", "Cycle Map");
|
||||
private static final int PARALLEL_REQUESTS_LIMIT = 8;
|
||||
private static final String PROTOCOL = "https";
|
||||
private static final int ZOOM_LEVEL_MAX = 22;
|
||||
private static final int ZOOM_LEVEL_MAX = 19;
|
||||
private static final int ZOOM_LEVEL_MIN = 0;
|
||||
|
||||
private String mapName;
|
||||
private String name;
|
||||
private final String mapName;
|
||||
private final String name;
|
||||
|
||||
public ThunderforestTileSource(String mapName, String name) {
|
||||
private ThunderforestTileSource(String mapName, String name) {
|
||||
super(new String[]{"tile.thunderforest.com"}, 443);
|
||||
this.mapName = mapName;
|
||||
this.name = name;
|
||||
|
||||
48
app/src/main/java/de/tadris/fitness/osm/GpsTraceLatLong.java
Normal file
48
app/src/main/java/de/tadris/fitness/osm/GpsTraceLatLong.java
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.osm;
|
||||
|
||||
import de.tadris.fitness.data.WorkoutSample;
|
||||
import de.westnordost.osmapi.map.data.LatLon;
|
||||
|
||||
class GpsTraceLatLong implements LatLon {
|
||||
|
||||
private final double latitude;
|
||||
private final double longitude;
|
||||
|
||||
private GpsTraceLatLong(double latitude, double longitude) {
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
public GpsTraceLatLong(WorkoutSample sample) {
|
||||
this(sample.lat, sample.lon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
}
|
||||
151
app/src/main/java/de/tadris/fitness/osm/OAuthAuthentication.java
Normal file
151
app/src/main/java/de/tadris/fitness/osm/OAuthAuthentication.java
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.osm;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.provider.Browser;
|
||||
import android.text.InputType;
|
||||
import android.widget.EditText;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.view.ProgressDialogController;
|
||||
import oauth.signpost.OAuth;
|
||||
import oauth.signpost.OAuthConsumer;
|
||||
import oauth.signpost.OAuthProvider;
|
||||
import oauth.signpost.exception.OAuthException;
|
||||
|
||||
public class OAuthAuthentication {
|
||||
|
||||
private final OAuthConsumer oAuthConsumer = OAuthUrlProvider.getDefaultConsumer();
|
||||
private final OAuthProvider oAuthProvider = OAuthUrlProvider.getDefaultProvider();
|
||||
|
||||
private final Handler handler;
|
||||
private final Activity activity;
|
||||
private final ProgressDialogController dialogController;
|
||||
private final SharedPreferences preferences;
|
||||
private final OAuthAuthenticationListener listener;
|
||||
|
||||
public OAuthAuthentication(Handler handler, Activity activity, OAuthAuthenticationListener listener) {
|
||||
this.handler = handler;
|
||||
this.activity = activity;
|
||||
dialogController= new ProgressDialogController(activity, activity.getString(R.string.uploading));
|
||||
this.preferences= activity.getSharedPreferences("osm_oauth", Context.MODE_PRIVATE);
|
||||
this.listener= listener;
|
||||
}
|
||||
|
||||
public void authenticateIfNecessary(){
|
||||
if(isAuthenticated()){
|
||||
loadAccessToken();
|
||||
listener.authenticationComplete(oAuthConsumer);
|
||||
}else{
|
||||
retrieveRequestToken();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAuthenticated(){
|
||||
return preferences.getBoolean("authenticated", false);
|
||||
}
|
||||
|
||||
private void retrieveRequestToken(){
|
||||
dialogController.show();
|
||||
dialogController.setIndeterminate(true);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String authUrl = oAuthProvider.retrieveRequestToken(oAuthConsumer, OAuth.OUT_OF_BAND);
|
||||
handler.post(() -> {
|
||||
Intent intent= new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl));
|
||||
intent.putExtra(Browser.EXTRA_APPLICATION_ID, activity.getPackageName());
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
activity.startActivity(intent);
|
||||
showEnterVerificationCodeDialog();
|
||||
dialogController.cancel();
|
||||
});
|
||||
} catch (OAuthException e) {
|
||||
e.printStackTrace();
|
||||
handler.post(() -> {
|
||||
dialogController.cancel();
|
||||
listener.authenticationFailed();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void showEnterVerificationCodeDialog(){
|
||||
EditText editText= new EditText(activity);
|
||||
editText.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
editText.setSingleLine(true);
|
||||
|
||||
AlertDialog dialog= new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.enterVerificationCode).setView(editText).setPositiveButton(R.string.okay, (dialogInterface, i) -> {
|
||||
new Thread(() -> retrieveAccessToken(editText.getText().toString().trim())).start();
|
||||
dialogInterface.cancel();
|
||||
}).setNegativeButton(R.string.cancel, null).setCancelable(false).create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void loadAccessToken(){
|
||||
oAuthConsumer.setTokenWithSecret(preferences.getString("accessToken", ""),
|
||||
preferences.getString("tokenSecret", ""));
|
||||
}
|
||||
|
||||
private void saveAccessToken(){
|
||||
preferences.edit()
|
||||
.putString("accessToken", oAuthConsumer.getToken())
|
||||
.putString("tokenSecret", oAuthConsumer.getTokenSecret())
|
||||
.putBoolean("authenticated", true).apply();
|
||||
}
|
||||
|
||||
void clearAccessToken() {
|
||||
preferences.edit().putBoolean("authenticated", false).apply();
|
||||
}
|
||||
|
||||
private void retrieveAccessToken(String verificationCode){
|
||||
handler.post(dialogController::show);
|
||||
try{
|
||||
oAuthProvider.retrieveAccessToken(oAuthConsumer, verificationCode);
|
||||
handler.post(() -> {
|
||||
dialogController.cancel();
|
||||
saveAccessToken();
|
||||
listener.authenticationComplete(oAuthConsumer);
|
||||
});
|
||||
}catch (OAuthException e){
|
||||
handler.post(() -> {
|
||||
dialogController.cancel();
|
||||
listener.authenticationFailed();
|
||||
});
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public interface OAuthAuthenticationListener{
|
||||
|
||||
void authenticationFailed();
|
||||
|
||||
void authenticationComplete(OAuthConsumer consumer);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.osm;
|
||||
|
||||
import oauth.signpost.OAuthConsumer;
|
||||
import oauth.signpost.OAuthProvider;
|
||||
import oauth.signpost.basic.DefaultOAuthConsumer;
|
||||
import oauth.signpost.basic.DefaultOAuthProvider;
|
||||
|
||||
class OAuthUrlProvider {
|
||||
|
||||
static private final String CONSUMER_KEY= "jFL9grFmAo5ZS720YDDRXdSOb7F0IZQf9lnY1PHq";
|
||||
static private final String CONSUMER_SECRET= "oH969vYW60fZLco6E09UQl3uFXqjl4siQbOL0q9q";
|
||||
|
||||
static OAuthConsumer getDefaultConsumer(){
|
||||
return new DefaultOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
|
||||
}
|
||||
|
||||
static OAuthProvider getDefaultProvider(){
|
||||
return new DefaultOAuthProvider(URL_TOKEN_REQUEST, URL_TOKEN_ACCESS, URL_AUTHORIZE);
|
||||
}
|
||||
|
||||
static private final String URL_TOKEN_REQUEST= "https://www.openstreetmap.org/oauth/request_token";
|
||||
static private final String URL_TOKEN_ACCESS= "https://www.openstreetmap.org/oauth/access_token";
|
||||
static private final String URL_AUTHORIZE= "https://www.openstreetmap.org/oauth/authorize";
|
||||
|
||||
|
||||
}
|
||||
139
app/src/main/java/de/tadris/fitness/osm/OsmTraceUploader.java
Normal file
139
app/src/main/java/de/tadris/fitness/osm/OsmTraceUploader.java
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.osm;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.data.WorkoutSample;
|
||||
import de.tadris.fitness.view.ProgressDialogController;
|
||||
import de.westnordost.osmapi.OsmConnection;
|
||||
import de.westnordost.osmapi.common.errors.OsmAuthorizationException;
|
||||
import de.westnordost.osmapi.traces.GpsTraceDetails;
|
||||
import de.westnordost.osmapi.traces.GpsTracesDao;
|
||||
import de.westnordost.osmapi.traces.GpsTrackpoint;
|
||||
import oauth.signpost.OAuthConsumer;
|
||||
|
||||
public class OsmTraceUploader {
|
||||
|
||||
private static final int CUT_DISTANCE= 300;
|
||||
|
||||
private final Activity activity;
|
||||
private final Handler handler;
|
||||
private final Workout workout;
|
||||
private final List<WorkoutSample> samples;
|
||||
private final GpsTraceDetails.Visibility visibility;
|
||||
private final OAuthConsumer consumer;
|
||||
private final boolean cut;
|
||||
private final ProgressDialogController dialogController;
|
||||
private final String description;
|
||||
|
||||
public OsmTraceUploader(Activity activity, Handler handler, Workout workout, List<WorkoutSample> samples, GpsTraceDetails.Visibility visibility, OAuthConsumer consumer, boolean cut, String description) {
|
||||
this.activity = activity;
|
||||
this.handler = handler;
|
||||
this.workout = workout;
|
||||
this.samples = samples;
|
||||
this.visibility = visibility;
|
||||
this.consumer = consumer;
|
||||
this.cut = cut;
|
||||
this.description= description;
|
||||
this.dialogController= new ProgressDialogController(activity, activity.getString(R.string.uploading));
|
||||
}
|
||||
|
||||
private void cut(){
|
||||
cut(false);
|
||||
cut(true);
|
||||
}
|
||||
|
||||
private void cut(boolean last){
|
||||
double distance= 0;
|
||||
int count= 0;
|
||||
WorkoutSample lastSample= samples.remove(last ? samples.size()-1 : 0);
|
||||
while(distance < CUT_DISTANCE){
|
||||
WorkoutSample currentSample= samples.remove(last ? samples.size()-1 : 0);
|
||||
distance+= lastSample.toLatLong().sphericalDistance(currentSample.toLatLong());
|
||||
count++;
|
||||
lastSample= currentSample;
|
||||
}
|
||||
Log.d("Uploader", "Cutted " + (last ? "last" : "first") + " " + count + " Samples (" + distance + " meters)");
|
||||
}
|
||||
|
||||
public void upload(){
|
||||
new Thread(() -> {
|
||||
try {
|
||||
executeTask();
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
handler.post(() -> {
|
||||
@StringRes int textRes = R.string.uploadFailed;
|
||||
|
||||
if (e instanceof OsmAuthorizationException) {
|
||||
textRes = R.string.uploadFailedOsmNotAuthorized;
|
||||
new OAuthAuthentication(handler, activity, null).clearAccessToken();
|
||||
}
|
||||
|
||||
Toast.makeText(activity, textRes, Toast.LENGTH_LONG).show();
|
||||
dialogController.cancel();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void executeTask(){
|
||||
handler.post(dialogController::show);
|
||||
setProgress(0);
|
||||
if(cut){ cut(); }
|
||||
setProgress(20);
|
||||
OsmConnection osm = new OsmConnection(
|
||||
"https://api.openstreetmap.org/api/0.6/", "FitoTrack", consumer);
|
||||
|
||||
List<GpsTrackpoint> trackpoints= new ArrayList<>();
|
||||
|
||||
for(WorkoutSample sample : samples){
|
||||
GpsTrackpoint trackpoint= new GpsTrackpoint(new GpsTraceLatLong(sample));
|
||||
trackpoint.time= new Date(sample.absoluteTime);
|
||||
trackpoint.elevation= (float)sample.elevation;
|
||||
trackpoints.add(trackpoint);
|
||||
}
|
||||
setProgress(25);
|
||||
new GpsTracesDao(osm).create(workout.getDateString(), visibility, description, Collections.singletonList("FitoTrack"), trackpoints);
|
||||
setProgress(100);
|
||||
handler.post(() -> {
|
||||
Toast.makeText(activity, R.string.uploadSuccessful, Toast.LENGTH_LONG).show();
|
||||
dialogController.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
private void setProgress(int progress){
|
||||
handler.post(() -> dialogController.setProgress(progress));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.tadris.fitness.location;
|
||||
package de.tadris.fitness.recording;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.Service;
|
||||
@ -51,9 +51,9 @@ public class LocationListener extends Service {
|
||||
private static final int LOCATION_INTERVAL = 1000;
|
||||
|
||||
private class LocationChangedListener implements android.location.LocationListener {
|
||||
Location mLastLocation;
|
||||
final Location mLastLocation;
|
||||
|
||||
public LocationChangedListener(String provider) {
|
||||
LocationChangedListener(String provider) {
|
||||
Log.i(TAG, "LocationListener " + provider);
|
||||
mLastLocation = new Location(provider);
|
||||
}
|
||||
@ -83,7 +83,7 @@ public class LocationListener extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
LocationChangedListener gpsListener= new LocationChangedListener(LocationManager.GPS_PROVIDER);
|
||||
private final LocationChangedListener gpsListener = new LocationChangedListener(LocationManager.GPS_PROVIDER);
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent arg0) {
|
||||
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.recording;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
|
||||
public class PressureService extends Service {
|
||||
|
||||
private static final String TAG = "PressureService";
|
||||
|
||||
private SensorManager sensorManager;
|
||||
private Instance instance;
|
||||
private Sensor pressureSensor;
|
||||
private PressureListener pressureListener;
|
||||
|
||||
private class PressureListener implements SensorEventListener {
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
instance.lastPressure= event.values[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) { }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Log.d(TAG, "onCreate");
|
||||
super.onCreate();
|
||||
instance= Instance.getInstance(this);
|
||||
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
|
||||
|
||||
pressureSensor= sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
|
||||
|
||||
pressureListener= new PressureListener();
|
||||
|
||||
if (pressureSensor != null){
|
||||
instance.pressureAvailable= true;
|
||||
sensorManager.registerListener(pressureListener, pressureSensor, SensorManager.SENSOR_DELAY_NORMAL);
|
||||
} else {
|
||||
instance.pressureAvailable= false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.d(TAG, "onStartCommand");
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.d(TAG, "onDestroy");
|
||||
super.onDestroy();
|
||||
sensorManager.unregisterListener(pressureListener);
|
||||
}
|
||||
|
||||
@Nullable @Override public IBinder onBind(Intent intent) { return null; }
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -17,7 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.tadris.fitness.location;
|
||||
package de.tadris.fitness.recording;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
@ -31,27 +31,21 @@ 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;
|
||||
import de.tadris.fitness.data.WorkoutType;
|
||||
import de.tadris.fitness.util.CalorieCalculator;
|
||||
|
||||
public class WorkoutRecorder implements LocationListener.LocationChangeListener {
|
||||
|
||||
private static int getMinDistance(String workoutType){
|
||||
switch (workoutType){
|
||||
case Workout.WORKOUT_TYPE_HIKING:
|
||||
case Workout.WORKOUT_TYPE_RUNNING:
|
||||
return 8;
|
||||
case Workout.WORKOUT_TYPE_CYCLING:
|
||||
return 15;
|
||||
default: return 10;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int PAUSE_TIME= 10000;
|
||||
|
||||
private Context context;
|
||||
private Workout workout;
|
||||
/**
|
||||
* Time after which the workout is stopped and saved automatically because there is no activity anymore
|
||||
*/
|
||||
private static final int AUTO_STOP_TIMEOUT= 1000*60*60*20; // 20 minutes
|
||||
|
||||
private final Context context;
|
||||
private final Workout workout;
|
||||
private RecordingState state;
|
||||
private final List<WorkoutSample> samples= new ArrayList<>();
|
||||
private long time= 0;
|
||||
@ -60,25 +54,26 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
|
||||
private long lastPause= 0;
|
||||
private long lastSampleTime= 0;
|
||||
private double distance= 0;
|
||||
private boolean hasBegan = false;
|
||||
private boolean hasBegun = false;
|
||||
|
||||
private static final double SIGNAL_BAD_THRESHOLD= 20; // In meters
|
||||
private static final int SIGNAL_LOST_THRESHOLD= 10000; // In milliseconds
|
||||
private Location lastFix= null;
|
||||
private GpsStateChangedListener gpsStateChangedListener;
|
||||
private final WorkoutRecorderListener workoutRecorderListener;
|
||||
private GpsState gpsState= GpsState.SIGNAL_LOST;
|
||||
|
||||
public WorkoutRecorder(Context context, String workoutType, GpsStateChangedListener gpsStateChangedListener) {
|
||||
public WorkoutRecorder(Context context, WorkoutType workoutType, WorkoutRecorderListener workoutRecorderListener) {
|
||||
this.context= context;
|
||||
this.state= RecordingState.IDLE;
|
||||
this.gpsStateChangedListener= gpsStateChangedListener;
|
||||
this.workoutRecorderListener = workoutRecorderListener;
|
||||
|
||||
this.workout= new Workout();
|
||||
workout.edited = false;
|
||||
|
||||
// Default values
|
||||
this.workout.comment= "";
|
||||
|
||||
this.workout.workoutType= workoutType;
|
||||
this.workout.setWorkoutType(workoutType);
|
||||
}
|
||||
|
||||
public void start(){
|
||||
@ -107,8 +102,15 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
|
||||
synchronized (samples){
|
||||
if(samples.size() > 2){
|
||||
WorkoutSample lastSample= samples.get(samples.size()-1);
|
||||
if(System.currentTimeMillis() - lastSampleTime > PAUSE_TIME){
|
||||
if(state == RecordingState.RUNNING){
|
||||
long timeDiff= System.currentTimeMillis() - lastSampleTime;
|
||||
if(timeDiff > AUTO_STOP_TIMEOUT){
|
||||
if(isActive()){
|
||||
stop();
|
||||
save();
|
||||
workoutRecorderListener.onAutoStop();
|
||||
}
|
||||
}else if(timeDiff > PAUSE_TIME){
|
||||
if (state == RecordingState.RUNNING && gpsState != GpsState.SIGNAL_LOST) {
|
||||
pause();
|
||||
}
|
||||
}else{
|
||||
@ -123,7 +125,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}, "WorkoutWatchdog").start();
|
||||
}
|
||||
|
||||
private void checkSignalState(){
|
||||
@ -139,7 +141,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
|
||||
state= GpsState.SIGNAL_OKAY;
|
||||
}
|
||||
if(state != gpsState){
|
||||
gpsStateChangedListener.onGPSStateChanged(gpsState, state);
|
||||
workoutRecorderListener.onGPSStateChanged(gpsState, state);
|
||||
gpsState= state;
|
||||
}
|
||||
}
|
||||
@ -153,7 +155,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
|
||||
}
|
||||
}
|
||||
|
||||
public void pause(){
|
||||
private void pause() {
|
||||
if(state == RecordingState.RUNNING){
|
||||
Log.i("Recorder", "Pause");
|
||||
state= RecordingState.PAUSED;
|
||||
@ -181,7 +183,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
|
||||
}
|
||||
Log.i("Recorder", "Save");
|
||||
synchronized (samples){
|
||||
WorkoutManager.insertWorkout(context, workout, samples);
|
||||
new WorkoutSaver(context, workout, samples).saveWorkout();
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,49 +199,58 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
|
||||
if(isActive()){
|
||||
double distance= 0;
|
||||
if(getSampleCount() > 0){
|
||||
// Checks whether the minimum distance to last sample was reached
|
||||
// and if the time difference to the last sample is too small
|
||||
synchronized (samples){
|
||||
WorkoutSample lastSample= samples.get(samples.size() - 1);
|
||||
distance= LocationListener.locationToLatLong(location).sphericalDistance(new LatLong(lastSample.lat, lastSample.lon));
|
||||
long timediff= lastSample.absoluteTime - location.getTime();
|
||||
if(distance < getMinDistance(workout.workoutType) && timediff < 500){
|
||||
if (distance < workout.getWorkoutType().minDistance && timediff < 500) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastSampleTime= System.currentTimeMillis();
|
||||
if(state == RecordingState.RUNNING && location.getTime() > workout.start){
|
||||
if(samples.size() == 2 && !hasBegan){
|
||||
lastResume= System.currentTimeMillis();
|
||||
workout.start= System.currentTimeMillis();
|
||||
lastPause= 0;
|
||||
time= 0;
|
||||
pauseTime= 0;
|
||||
this.distance= 0;
|
||||
samples.clear();
|
||||
|
||||
hasBegan = true; // Do not clear a second time
|
||||
if(samples.size() == 2 && !hasBegun){
|
||||
initialClearValues();
|
||||
hasBegun = true; // Do not clear a second time
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
addToSamples(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance in meters
|
||||
*/
|
||||
public int getDistance(){
|
||||
private void addToSamples(Location location){
|
||||
WorkoutSample sample= new WorkoutSample();
|
||||
sample.lat= location.getLatitude();
|
||||
sample.lon= location.getLongitude();
|
||||
sample.elevation= location.getAltitude();
|
||||
sample.speed= location.getSpeed();
|
||||
sample.relativeTime= location.getTime() - workout.start - pauseTime;
|
||||
sample.absoluteTime= location.getTime();
|
||||
if(Instance.getInstance(context).pressureAvailable){
|
||||
sample.tmpPressure= Instance.getInstance(context).lastPressure;
|
||||
}else{
|
||||
sample.tmpPressure= -1;
|
||||
}
|
||||
synchronized (samples){
|
||||
samples.add(sample);
|
||||
}
|
||||
}
|
||||
|
||||
private void initialClearValues(){
|
||||
lastResume= System.currentTimeMillis();
|
||||
workout.start= System.currentTimeMillis();
|
||||
lastPause= 0;
|
||||
time= 0;
|
||||
pauseTime= 0;
|
||||
this.distance= 0;
|
||||
samples.clear();
|
||||
}
|
||||
|
||||
public int getDistanceInMeters() {
|
||||
return (int)distance;
|
||||
}
|
||||
|
||||
@ -282,6 +293,10 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
|
||||
workout.comment= comment;
|
||||
}
|
||||
|
||||
public boolean isPaused(){
|
||||
return state == RecordingState.PAUSED;
|
||||
}
|
||||
|
||||
|
||||
enum RecordingState{
|
||||
IDLE, RUNNING, PAUSED, STOPPED
|
||||
@ -292,15 +307,16 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener
|
||||
SIGNAL_OKAY(Color.GREEN),
|
||||
SIGNAL_BAD(Color.YELLOW);
|
||||
|
||||
public int color;
|
||||
public final int color;
|
||||
|
||||
GpsState(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
public interface GpsStateChangedListener{
|
||||
public interface WorkoutRecorderListener {
|
||||
void onGPSStateChanged(GpsState oldState, GpsState state);
|
||||
void onAutoStop();
|
||||
}
|
||||
|
||||
}
|
||||
209
app/src/main/java/de/tadris/fitness/recording/WorkoutSaver.java
Normal file
209
app/src/main/java/de/tadris/fitness/recording/WorkoutSaver.java
Normal file
@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.recording;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.SensorManager;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.data.AppDatabase;
|
||||
import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.data.WorkoutSample;
|
||||
import de.tadris.fitness.util.AltitudeCorrection;
|
||||
import de.tadris.fitness.util.CalorieCalculator;
|
||||
|
||||
class WorkoutSaver {
|
||||
|
||||
private final Context context;
|
||||
private final Workout workout;
|
||||
private final List<WorkoutSample> samples;
|
||||
private final AppDatabase db;
|
||||
|
||||
public WorkoutSaver(Context context, Workout workout, List<WorkoutSample> samples) {
|
||||
this.context = context;
|
||||
this.workout = workout;
|
||||
this.samples = samples;
|
||||
db= Instance.getInstance(context).db;
|
||||
}
|
||||
|
||||
public void saveWorkout(){
|
||||
setIds();
|
||||
clearSamplesWithSameTime();
|
||||
setSimpleValues();
|
||||
setTopSpeed();
|
||||
|
||||
setElevation();
|
||||
setAscentAndDescent();
|
||||
|
||||
setCalories();
|
||||
|
||||
storeInDatabase();
|
||||
}
|
||||
|
||||
private void setIds(){
|
||||
workout.id= System.currentTimeMillis();
|
||||
int i= 0;
|
||||
for(WorkoutSample sample : samples) {
|
||||
i++;
|
||||
sample.id = workout.id + i;
|
||||
sample.workoutId = workout.id;
|
||||
}
|
||||
}
|
||||
|
||||
private void clearSamplesWithSameTime(){
|
||||
for(int i= samples.size()-2; i >= 0; i--){
|
||||
WorkoutSample sample= samples.get(i);
|
||||
WorkoutSample lastSample= samples.get(i+1);
|
||||
if(sample.absoluteTime == lastSample.absoluteTime){
|
||||
samples.remove(lastSample);
|
||||
Log.i("WorkoutManager", "Removed samples at " + sample.absoluteTime + " rel: " + sample.relativeTime + "; " + lastSample.relativeTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setSimpleValues(){
|
||||
double length= 0;
|
||||
for(int i= 1; i < samples.size(); i++){
|
||||
double sampleLength= samples.get(i - 1).toLatLong().sphericalDistance(samples.get(i).toLatLong());
|
||||
length+= sampleLength;
|
||||
}
|
||||
workout.length= (int)length;
|
||||
workout.avgSpeed= ((double) workout.length) / ((double) workout.duration / 1000);
|
||||
workout.avgPace= ((double)workout.duration / 1000 / 60) / ((double) workout.length / 1000);
|
||||
}
|
||||
|
||||
private void setTopSpeed(){
|
||||
double topSpeed= 0;
|
||||
for(WorkoutSample sample : samples){
|
||||
if(sample.speed > topSpeed){
|
||||
topSpeed= sample.speed;
|
||||
}
|
||||
}
|
||||
workout.topSpeed= topSpeed;
|
||||
}
|
||||
|
||||
private void setElevation() {
|
||||
setCorrectedElevation();
|
||||
setPressureElevation();
|
||||
}
|
||||
|
||||
private void setCorrectedElevation() {
|
||||
// Please see the AltitudeCorrection.java for the reason of this
|
||||
try {
|
||||
int lat = (int) Math.round(samples.get(0).lat);
|
||||
int lon = (int) Math.round(samples.get(0).lon);
|
||||
AltitudeCorrection correction = new AltitudeCorrection(context, lat, lon);
|
||||
for (WorkoutSample sample : samples) {
|
||||
sample.elevation = correction.getHeightOverSeaLevel(sample.elevation);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// If we can't read the file, we cannot correct the values
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void setPressureElevation() {
|
||||
boolean pressureDataAvailable= samples.get(0).tmpPressure != -1;
|
||||
|
||||
if(!pressureDataAvailable){
|
||||
// Because pressure data isn't available we just use the use GPS elevation
|
||||
// in WorkoutSample.elevation which was already set
|
||||
return;
|
||||
}
|
||||
|
||||
double avgElevation= getAverageElevation();
|
||||
double avgPressure= getAveragePressure();
|
||||
|
||||
for(int i= 0; i < samples.size(); i++){
|
||||
WorkoutSample sample= samples.get(i);
|
||||
|
||||
// Altitude Difference to Average Elevation in meters
|
||||
float altitude_difference =
|
||||
SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, sample.tmpPressure) -
|
||||
SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, (float) avgPressure);
|
||||
sample.elevation= avgElevation + altitude_difference;
|
||||
}
|
||||
}
|
||||
|
||||
private double getAverageElevation(){
|
||||
return getAverageElevation(samples);
|
||||
}
|
||||
|
||||
private double getAverageElevation(List<WorkoutSample> samples){
|
||||
double elevationSum= 0; // Sum of elevation
|
||||
for(WorkoutSample sample : samples){
|
||||
elevationSum+= sample.elevation;
|
||||
}
|
||||
|
||||
return elevationSum / samples.size();
|
||||
}
|
||||
|
||||
private double getAveragePressure(){
|
||||
double pressureSum= 0;
|
||||
for(WorkoutSample sample : samples){
|
||||
pressureSum+= sample.tmpPressure;
|
||||
}
|
||||
return pressureSum / samples.size();
|
||||
}
|
||||
|
||||
private void setAscentAndDescent(){
|
||||
workout.ascent = 0;
|
||||
workout.descent = 0;
|
||||
|
||||
// First calculate a floating average to eliminate pressure noise to influence our ascent/descent
|
||||
int range = 7;
|
||||
for(int i= 0; i < samples.size(); i++){
|
||||
int minIndex = Math.max(i - range, 0);
|
||||
int maxIndex = Math.min(i + range, samples.size() - 1);
|
||||
samples.get(i).tmpElevation = getAverageElevation(samples.subList(minIndex, maxIndex));
|
||||
}
|
||||
|
||||
// Now sum up the ascent/descent
|
||||
for(int i= 0; i < samples.size(); i++) {
|
||||
WorkoutSample sample = samples.get(i);
|
||||
sample.elevation= sample.tmpElevation;
|
||||
if(i >= 1){
|
||||
WorkoutSample lastSample= samples.get(i-1);
|
||||
double diff= sample.elevation - lastSample.elevation;
|
||||
if(diff > 0){
|
||||
// If this sample is higher than the last one, add difference to ascent
|
||||
workout.ascent += diff;
|
||||
}else{
|
||||
// If this sample is lower than the last one, add difference to descent
|
||||
workout.descent += Math.abs(diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setCalories() {
|
||||
// Ascent has to be set previously
|
||||
workout.calorie = CalorieCalculator.calculateCalories(workout, Instance.getInstance(context).userPreferences.getUserWeight());
|
||||
}
|
||||
|
||||
private void storeInDatabase(){
|
||||
db.workoutDao().insertWorkoutAndSamples(workout, samples.toArray(new WorkoutSample[0]));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.recording.announcement;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import de.tadris.fitness.recording.WorkoutRecorder;
|
||||
|
||||
public abstract class Announcement {
|
||||
|
||||
private Context context;
|
||||
|
||||
Announcement(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("announcement_" + getId(), isEnabledByDefault());
|
||||
}
|
||||
|
||||
protected String getString(@StringRes int resId) {
|
||||
return context.getString(resId);
|
||||
}
|
||||
|
||||
public abstract String getId();
|
||||
|
||||
abstract boolean isEnabledByDefault();
|
||||
|
||||
abstract String getSpoken(WorkoutRecorder recorder);
|
||||
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.recording.announcement;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.recording.WorkoutRecorder;
|
||||
import de.tadris.fitness.util.unit.UnitUtils;
|
||||
|
||||
public class AnnouncementAverageSpeed extends Announcement {
|
||||
|
||||
public AnnouncementAverageSpeed(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "avgSpeed";
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getSpoken(WorkoutRecorder recorder) {
|
||||
String avgSpeed = UnitUtils.getSpeed(recorder.getAvgSpeed());
|
||||
return getString(R.string.workoutAvgSpeedLong) + ": " + avgSpeed + ".";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.recording.announcement;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.recording.WorkoutRecorder;
|
||||
import de.tadris.fitness.util.unit.UnitUtils;
|
||||
|
||||
public class AnnouncementDistance extends Announcement {
|
||||
|
||||
public AnnouncementDistance(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "distance";
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getSpoken(WorkoutRecorder recorder) {
|
||||
final String distance = UnitUtils.getDistance(recorder.getDistanceInMeters());
|
||||
return getString(R.string.workoutDistance) + ": " + distance + ".";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.recording.announcement;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.recording.WorkoutRecorder;
|
||||
|
||||
public class AnnouncementDuration extends Announcement {
|
||||
|
||||
public AnnouncementDuration(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "duration";
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getSpoken(WorkoutRecorder recorder) {
|
||||
return getString(R.string.workoutDuration) + ": " + getSpokenTime(recorder.getDuration()) + ".";
|
||||
}
|
||||
|
||||
private String getSpokenTime(long duration) {
|
||||
final long minute = 1000L * 60;
|
||||
final long hour = minute * 60;
|
||||
|
||||
StringBuilder spokenTime = new StringBuilder();
|
||||
|
||||
if (duration > hour) {
|
||||
long hours = duration / hour;
|
||||
duration = duration % hour; // Set duration to the rest
|
||||
spokenTime.append(hours).append(" ");
|
||||
spokenTime.append(getString(hours == 1 ? R.string.timeHourSingular : R.string.timeHourPlural)).append(" ")
|
||||
.append(getString(R.string.and)).append(" ");
|
||||
}
|
||||
long minutes = duration / minute;
|
||||
spokenTime.append(minutes).append(" ");
|
||||
spokenTime.append(getString(minutes == 1 ? R.string.timeMinuteSingular : R.string.timeMinutePlural));
|
||||
|
||||
return spokenTime.toString();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.recording.announcement;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.recording.WorkoutRecorder;
|
||||
|
||||
public class AnnouncementGPSStatus extends Announcement {
|
||||
|
||||
public AnnouncementGPSStatus(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "gps-lost";
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getSpoken(WorkoutRecorder recorder) {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getSpokenGPSLost() {
|
||||
return getString(R.string.gpsLost);
|
||||
}
|
||||
|
||||
public String getSpokenGPSFound() {
|
||||
return getString(R.string.gpsFound);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.recording.announcement;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AnnouncementManager {
|
||||
|
||||
private Context context;
|
||||
private List<Announcement> announcements = new ArrayList<>();
|
||||
|
||||
public AnnouncementManager(Context context) {
|
||||
this.context = context;
|
||||
addAnnouncements();
|
||||
}
|
||||
|
||||
private void addAnnouncements() {
|
||||
announcements.add(new AnnouncementGPSStatus(context));
|
||||
announcements.add(new AnnouncementDuration(context));
|
||||
announcements.add(new AnnouncementDistance(context));
|
||||
announcements.add(new AnnouncementAverageSpeed(context));
|
||||
}
|
||||
|
||||
public List<Announcement> getAnnouncements() {
|
||||
return announcements;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -17,17 +17,26 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.tadris.fitness.map.tilesource;
|
||||
package de.tadris.fitness.recording.announcement;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
public class TileSources {
|
||||
public enum AnnouncementMode {
|
||||
|
||||
public static FitoTrackTileSource[] tileSources= new FitoTrackTileSource[]{
|
||||
MapnikTileSource.INSTANCE, HumanitarianTileSource.INSTANCE, ThunderforestTileSource.OUTDOORS, ThunderforestTileSource.CYLE_MAP
|
||||
};
|
||||
ALWAYS,
|
||||
HEADPHONES;
|
||||
|
||||
public enum Purpose{
|
||||
DEFAULT, OUTDOOR, CYCLING
|
||||
static AnnouncementMode getCurrentMode(Context context) {
|
||||
String mode = PreferenceManager.getDefaultSharedPreferences(context).getString("announcementMode", "headphones");
|
||||
assert mode != null;
|
||||
switch (mode) {
|
||||
case "always":
|
||||
return ALWAYS;
|
||||
default:
|
||||
case "headphones":
|
||||
return HEADPHONES;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.recording.announcement;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothHeadset;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.speech.tts.UtteranceProgressListener;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.data.UserPreferences;
|
||||
import de.tadris.fitness.recording.WorkoutRecorder;
|
||||
import de.tadris.fitness.util.unit.UnitUtils;
|
||||
|
||||
public class VoiceAnnouncements {
|
||||
|
||||
private TextToSpeech textToSpeech;
|
||||
private boolean ttsAvailable;
|
||||
private VoiceAnnouncementCallback callback;
|
||||
private final AnnouncementManager manager;
|
||||
|
||||
private long lastSpokenUpdateTime = 0;
|
||||
private int lastSpokenUpdateDistance = 0;
|
||||
|
||||
private final AnnouncementMode currentMode;
|
||||
private final long intervalTime;
|
||||
private final int intervalInMeters;
|
||||
|
||||
private final AudioManager audioManager;
|
||||
|
||||
public VoiceAnnouncements(Context context, VoiceAnnouncementCallback callback) {
|
||||
this.callback = callback;
|
||||
UserPreferences prefs = Instance.getInstance(context).userPreferences;
|
||||
textToSpeech = new TextToSpeech(context, this::ttsReady);
|
||||
|
||||
this.intervalTime = 60 * 1000 * prefs.getSpokenUpdateTimePeriod();
|
||||
this.intervalInMeters = (int) (1000.0 / UnitUtils.CHOSEN_SYSTEM.getDistanceFromKilometers(1) * prefs.getSpokenUpdateDistancePeriod());
|
||||
|
||||
this.manager = new AnnouncementManager(context);
|
||||
this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
|
||||
this.currentMode = AnnouncementMode.getCurrentMode(context);
|
||||
}
|
||||
|
||||
private void ttsReady(int status) {
|
||||
ttsAvailable = status == TextToSpeech.SUCCESS && textToSpeech.setLanguage(Locale.getDefault()) >= 0;
|
||||
if (ttsAvailable) {
|
||||
textToSpeech.setOnUtteranceProgressListener(new TextToSpeechListener());
|
||||
}
|
||||
callback.onVoiceAnnouncementIsReady(ttsAvailable);
|
||||
}
|
||||
|
||||
public void check(WorkoutRecorder recorder) {
|
||||
if (!ttsAvailable) {
|
||||
return;
|
||||
} // Cannot speak
|
||||
|
||||
boolean shouldSpeak = false;
|
||||
|
||||
if (intervalTime != 0 && recorder.getDuration() - lastSpokenUpdateTime > intervalTime) {
|
||||
shouldSpeak = true;
|
||||
}
|
||||
if (intervalInMeters != 0 && recorder.getDistanceInMeters() - lastSpokenUpdateDistance > intervalInMeters) {
|
||||
shouldSpeak = true;
|
||||
}
|
||||
|
||||
if (shouldSpeak) {
|
||||
speak(recorder);
|
||||
}
|
||||
}
|
||||
|
||||
private void speak(WorkoutRecorder recorder) {
|
||||
for (Announcement announcement : manager.getAnnouncements()) {
|
||||
speak(recorder, announcement);
|
||||
}
|
||||
|
||||
lastSpokenUpdateTime = recorder.getDuration();
|
||||
lastSpokenUpdateDistance = recorder.getDistanceInMeters();
|
||||
}
|
||||
|
||||
private void speak(WorkoutRecorder recorder, Announcement announcement) {
|
||||
if (!announcement.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
String text = announcement.getSpoken(recorder);
|
||||
if (!text.equals("")) {
|
||||
speak(text);
|
||||
}
|
||||
}
|
||||
|
||||
private int speakId = 1;
|
||||
|
||||
public void speak(String text) {
|
||||
if (!ttsAvailable) {
|
||||
// Cannot speak
|
||||
return;
|
||||
}
|
||||
if (currentMode == AnnouncementMode.HEADPHONES && !isHeadsetOn()) {
|
||||
// Not allowed to speak
|
||||
return;
|
||||
}
|
||||
Log.d("Recorder", "TTS speaks: " + text);
|
||||
textToSpeech.speak(text, TextToSpeech.QUEUE_ADD, null, "announcement" + (++speakId));
|
||||
}
|
||||
|
||||
private boolean isHeadsetOn() {
|
||||
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
boolean bluetoothHeadsetConnected = mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()
|
||||
&& mBluetoothAdapter.getProfileConnectionState(BluetoothHeadset.HEADSET) == BluetoothHeadset.STATE_CONNECTED;
|
||||
|
||||
return audioManager.isWiredHeadsetOn() || bluetoothHeadsetConnected;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
textToSpeech.shutdown();
|
||||
}
|
||||
|
||||
private class TextToSpeechListener extends UtteranceProgressListener {
|
||||
|
||||
@Override
|
||||
public void onStart(String utteranceId) {
|
||||
audioManager.requestAudioFocus(null, AudioManager.STREAM_SYSTEM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDone(String utteranceId) {
|
||||
audioManager.abandonAudioFocus(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String utteranceId) {
|
||||
}
|
||||
}
|
||||
|
||||
public interface VoiceAnnouncementCallback {
|
||||
void onVoiceAnnouncementIsReady(boolean available);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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 org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
|
||||
/**
|
||||
* This class has the task to correct the altitude.
|
||||
* <p>
|
||||
* In Germany or UK for example the GPS height differs
|
||||
* roughly 50m from the real elevation over sea level.
|
||||
* <p>
|
||||
* The altitude given by GPS is the altitude over the WGS84 reference ellipsoid
|
||||
* but we want the height over the sea level. That's why we have to correct the height.
|
||||
* Luckily I found a file containing the corrections for all places around the world.
|
||||
* <p>
|
||||
* The geoids.csv is from https://github.com/vectorstofinal/geoid_heights licensed under MIT
|
||||
*/
|
||||
public class AltitudeCorrection {
|
||||
|
||||
private Context context;
|
||||
private int latitude, longitude;
|
||||
private double offset; // Basically how much higher the sea-level than the ellipsoid is
|
||||
|
||||
public AltitudeCorrection(Context context, int latitude, int longitude) throws IOException {
|
||||
this.context = context;
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
findOffset();
|
||||
}
|
||||
|
||||
private void findOffset() throws IOException {
|
||||
InputStream inputStream = context.getResources().openRawResource(R.raw.geoids);
|
||||
List<String> list = IOUtils.readLines(inputStream, StandardCharsets.UTF_8);
|
||||
for (String line : list) {
|
||||
String[] data = line.split(",");
|
||||
int lat = Integer.parseInt(data[0]);
|
||||
int lon = Integer.parseInt(data[1]);
|
||||
double offset = Double.parseDouble(data[2]);
|
||||
if (lat == this.latitude && lon == this.longitude) {
|
||||
this.offset = offset;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double getHeightOverSeaLevel(double heightOverEllipsoid) {
|
||||
return heightOverEllipsoid - offset;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -20,12 +20,13 @@
|
||||
package de.tadris.fitness.util;
|
||||
|
||||
import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.data.WorkoutType;
|
||||
|
||||
public class CalorieCalculator {
|
||||
|
||||
/**
|
||||
*
|
||||
* workoutType, duration and avgSpeed of workout have to be set
|
||||
* workoutType, duration, ascent and avgSpeed of workout have to be set
|
||||
*
|
||||
* @param workout the workout
|
||||
* @param weight the weight of the person in kilogram
|
||||
@ -33,7 +34,8 @@ public class CalorieCalculator {
|
||||
*/
|
||||
public static int calculateCalories(Workout workout, double weight){
|
||||
double mins= (double)(workout.duration / 1000) / 60;
|
||||
return (int)(mins * (getMET(workout) * 3.5 * weight) / 200);
|
||||
int ascent= (int)workout.ascent; // 1 calorie per meter
|
||||
return (int)(mins * (getMET(workout) * 3.5 * weight) / 200) + ascent;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,38 +43,19 @@ public class CalorieCalculator {
|
||||
*
|
||||
* workoutType and avgSpeed of workout have to be set
|
||||
*
|
||||
* @param workout
|
||||
* Calculation currently ignores height.
|
||||
* @return MET
|
||||
*/
|
||||
public static double getMET(Workout workout){
|
||||
if(workout.workoutType.equals(Workout.WORKOUT_TYPE_RUNNING)){
|
||||
if(workout.avgSpeed < 3.2){
|
||||
return 1.5;
|
||||
}else if(workout.avgSpeed < 4.0){
|
||||
return 2.5;
|
||||
}else if(workout.avgSpeed < 4.8){
|
||||
return 3.25;
|
||||
}else if(workout.avgSpeed < 5.9){
|
||||
return 4.0;
|
||||
}else if(workout.avgSpeed < 7.0){
|
||||
return 5.0;
|
||||
}else if(workout.avgSpeed < 8.0){
|
||||
return 7.0;
|
||||
}else if(workout.avgSpeed < 9.6){
|
||||
return 9.0;
|
||||
}else if(workout.avgSpeed < 12.8){
|
||||
return 12.0;
|
||||
}else{
|
||||
return 16;
|
||||
}
|
||||
private static double getMET(Workout workout) {
|
||||
double speedInKmh= workout.avgSpeed * 3.6;
|
||||
WorkoutType type = workout.getWorkoutType();
|
||||
if (type == WorkoutType.RUNNING || type == WorkoutType.HIKING) {
|
||||
return Math.max(1.5, speedInKmh*1.117 - 2.1906);
|
||||
}
|
||||
if(workout.workoutType.equals(Workout.WORKOUT_TYPE_HIKING)){
|
||||
return 6.3;
|
||||
if (type == WorkoutType.CYCLING) {
|
||||
return Math.max(3, (speedInKmh-10) / 1.5);
|
||||
}
|
||||
if(workout.workoutType.equals(Workout.WORKOUT_TYPE_CYCLING)){
|
||||
return Math.max(3, (workout.avgSpeed-10) / 1.5);
|
||||
}
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -19,23 +19,23 @@
|
||||
|
||||
package de.tadris.fitness.util;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.data.Workout;
|
||||
|
||||
public class ThemeManager {
|
||||
public class DialogUtils {
|
||||
|
||||
public static int getThemeByWorkoutType(String type){
|
||||
switch (type){
|
||||
case Workout.WORKOUT_TYPE_RUNNING: return R.style.Running;
|
||||
case Workout.WORKOUT_TYPE_CYCLING: return R.style.Bicycling;
|
||||
case Workout.WORKOUT_TYPE_HIKING: return R.style.Hiking;
|
||||
default: return R.style.AppTheme;
|
||||
}
|
||||
public static void showDeleteWorkoutDialog(Context context, WorkoutDeleter deleter){
|
||||
new AlertDialog.Builder(context).setTitle(R.string.deleteWorkout)
|
||||
.setMessage(R.string.deleteWorkoutMessage)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete, (dialog, which) -> deleter.deleteWorkout())
|
||||
.create().show();
|
||||
}
|
||||
|
||||
public static int getThemeByWorkout(Workout workout){
|
||||
return getThemeByWorkoutType(workout.workoutType);
|
||||
public interface WorkoutDeleter{
|
||||
void deleteWorkout();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
78
app/src/main/java/de/tadris/fitness/util/FileUtils.java
Normal file
78
app/src/main/java/de/tadris/fitness/util/FileUtils.java
Normal file
@ -0,0 +1,78 @@
|
||||
package de.tadris.fitness.util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
public static void saveOrShareFile(Activity activity, Uri uri, String suffix) {
|
||||
String[] colors = {activity.getString(R.string.share), activity.getString(R.string.save)};
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setItems(colors, (dialog, which) -> {
|
||||
if (which == 0) {
|
||||
shareFile(activity, uri);
|
||||
} else {
|
||||
try {
|
||||
saveFile(activity, uri, suffix);
|
||||
Toast.makeText(activity, R.string.savedToDownloads, Toast.LENGTH_LONG).show();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Toast.makeText(activity, R.string.savingFailed, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private static void saveFile(Activity activity, Uri fileUri, String suffix) throws IOException {
|
||||
File target = new File(Environment.getExternalStorageDirectory(), "Download/fitotrack" + System.currentTimeMillis() + "." + suffix);
|
||||
if (!target.createNewFile()) {
|
||||
throw new IOException("Cannot write to file " + target);
|
||||
}
|
||||
copyFile(activity, fileUri, Uri.fromFile(target));
|
||||
}
|
||||
|
||||
private static void copyFile(Activity activity, Uri sourceUri, Uri targetUri) throws IOException {
|
||||
InputStream input = activity.getContentResolver().openInputStream(sourceUri);
|
||||
if (input == null) {
|
||||
throw new IOException("Source file not found");
|
||||
}
|
||||
OutputStream output = activity.getContentResolver().openOutputStream(targetUri);
|
||||
IOUtils.copy(input, output);
|
||||
}
|
||||
|
||||
private static void shareFile(Activity activity, Uri uri) {
|
||||
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
|
||||
intentShareFile.setDataAndType(uri, activity.getContentResolver().getType(uri));
|
||||
intentShareFile.putExtra(Intent.EXTRA_STREAM, uri);
|
||||
intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
activity.startActivity(Intent.createChooser(intentShareFile, activity.getString(R.string.shareFile)));
|
||||
|
||||
Log.d("Export", uri.toString());
|
||||
Log.d("Export", activity.getContentResolver().getType(uri));
|
||||
try {
|
||||
Log.d("Export", new BufferedInputStream(activity.getContentResolver().openInputStream(uri)).toString());
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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 android.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.StyleRes;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.data.WorkoutType;
|
||||
|
||||
public class FitoTrackThemes {
|
||||
|
||||
private static final int THEME_SETTING_LIGHT = 0;
|
||||
private static final int THEME_SETTING_DARK = 1;
|
||||
|
||||
private Context context;
|
||||
|
||||
public FitoTrackThemes(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
public int getDefaultTheme() {
|
||||
if (shouldUseLightMode()) {
|
||||
return R.style.AppTheme;
|
||||
} else {
|
||||
return R.style.AppThemeDark;
|
||||
}
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
public int getWorkoutTypeTheme(WorkoutType type) {
|
||||
if (shouldUseLightMode()) {
|
||||
return type.lightTheme;
|
||||
} else {
|
||||
return type.darkTheme;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldUseLightMode() {
|
||||
switch (getThemeSetting()) {
|
||||
default:
|
||||
case THEME_SETTING_LIGHT:
|
||||
return true;
|
||||
case THEME_SETTING_DARK:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int getThemeSetting() {
|
||||
String setting = PreferenceManager.getDefaultSharedPreferences(context).getString("themeSetting", String.valueOf(THEME_SETTING_LIGHT));
|
||||
assert setting != null;
|
||||
return Integer.parseInt(setting);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -28,7 +28,7 @@ import de.tadris.fitness.R;
|
||||
|
||||
public class NotificationHelper {
|
||||
|
||||
public static String CHANNEL_WORKOUT= "workout";
|
||||
public static final String CHANNEL_WORKOUT = "workout";
|
||||
|
||||
public static void createChannels(Context context){
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
* FitoTrack is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* FitoTrack is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.tadris.fitness.util;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.data.Workout;
|
||||
|
||||
public class WorkoutTypeCalculator {
|
||||
|
||||
public static int getType(Workout workout){
|
||||
if(workout.workoutType.equals(Workout.WORKOUT_TYPE_RUNNING)){
|
||||
if(workout.avgSpeed < 1.9){
|
||||
return R.string.workoutTypeWalking;
|
||||
}else if(workout.avgSpeed < 2.7){
|
||||
return R.string.workoutTypeJogging;
|
||||
}else{
|
||||
return R.string.workoutTypeRunning;
|
||||
}
|
||||
}
|
||||
if(workout.workoutType.equals(Workout.WORKOUT_TYPE_CYCLING)){
|
||||
return R.string.workoutTypeCycling;
|
||||
}
|
||||
if(workout.workoutType.equals(Workout.WORKOUT_TYPE_HIKING)){
|
||||
return R.string.workoutTypeHiking;
|
||||
}
|
||||
return R.string.workoutTypeUnknown;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,125 +0,0 @@
|
||||
/*
|
||||
* 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.export;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import de.tadris.fitness.Instance;
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.data.AppDatabase;
|
||||
import de.tadris.fitness.data.UserPreferences;
|
||||
import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.data.WorkoutSample;
|
||||
import de.tadris.fitness.util.unit.UnitUtils;
|
||||
|
||||
public class Exporter {
|
||||
|
||||
public static final int VERSION= 1;
|
||||
|
||||
public static void exportData(Context context, File output, ExportStatusListener listener) throws IOException {
|
||||
listener.onStatusChanged(0, context.getString(R.string.initialising));
|
||||
UserPreferences preferences= Instance.getInstance(context).userPreferences;
|
||||
AppDatabase database= Instance.getInstance(context).db;
|
||||
UnitUtils.setUnit(context);
|
||||
|
||||
FitoTrackDataContainer container= new FitoTrackDataContainer();
|
||||
container.version= VERSION;
|
||||
container.workouts= new ArrayList<>();
|
||||
container.samples= new ArrayList<>();
|
||||
|
||||
listener.onStatusChanged(10, context.getString(R.string.preferences));
|
||||
FitoTrackSettings settings= new FitoTrackSettings();
|
||||
settings.weight= preferences.getUserWeight();
|
||||
settings.preferredUnitSystem= String.valueOf(UnitUtils.CHOSEN_SYSTEM.getId());
|
||||
container.settings= settings;
|
||||
|
||||
listener.onStatusChanged(20, context.getString(R.string.workouts));
|
||||
container.workouts.addAll(Arrays.asList(database.workoutDao().getWorkouts()));
|
||||
listener.onStatusChanged(40, context.getString(R.string.locationData));
|
||||
container.samples.addAll(Arrays.asList(database.workoutDao().getSamples()));
|
||||
|
||||
listener.onStatusChanged(60, context.getString(R.string.converting));
|
||||
|
||||
XmlMapper mapper= new XmlMapper();
|
||||
mapper.writeValue(output, container);
|
||||
|
||||
listener.onStatusChanged(100, context.getString(R.string.finished));
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
public static void importData(Context context, Uri input, ExportStatusListener listener) throws IOException{
|
||||
listener.onStatusChanged(0, context.getString(R.string.loadingFile));
|
||||
XmlMapper xmlMapper = new XmlMapper();
|
||||
FitoTrackDataContainer container = xmlMapper.readValue(context.getContentResolver().openInputStream(input), FitoTrackDataContainer.class);
|
||||
|
||||
if(container.version != 1){
|
||||
throw new UnsupportedEncodingException("Version Code" + container.version + " is unsupported!");
|
||||
}
|
||||
|
||||
listener.onStatusChanged(40, context.getString(R.string.preferences));
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit().clear()
|
||||
.putInt("weight", container.settings.weight)
|
||||
.putString("unitSystem", container.settings.preferredUnitSystem)
|
||||
.commit();
|
||||
|
||||
AppDatabase database= Instance.getInstance(context).db;
|
||||
database.clearAllTables();
|
||||
|
||||
listener.onStatusChanged(60, context.getString(R.string.workouts));
|
||||
if(container.workouts != null){
|
||||
for(Workout workout : container.workouts){
|
||||
database.workoutDao().insertWorkout(workout);
|
||||
}
|
||||
}
|
||||
|
||||
listener.onStatusChanged(80, context.getString(R.string.locationData));
|
||||
if(container.samples != null){
|
||||
for(WorkoutSample sample : container.samples){
|
||||
database.workoutDao().insertSample(sample);
|
||||
}
|
||||
}
|
||||
|
||||
listener.onStatusChanged(100, context.getString(R.string.finished));
|
||||
}
|
||||
|
||||
|
||||
public interface ExportStatusListener{
|
||||
void onStatusChanged(int progress, String action);
|
||||
}
|
||||
|
||||
public static class UnsupportedVersionException extends Exception{
|
||||
public UnsupportedVersionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
package de.tadris.fitness.util.export;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.data.WorkoutSample;
|
||||
|
||||
@JacksonXmlRootElement(localName = "fito-track")
|
||||
public class FitoTrackDataContainer {
|
||||
|
||||
int version;
|
||||
List<Workout> workouts;
|
||||
List<WorkoutSample> samples;
|
||||
FitoTrackSettings settings;
|
||||
|
||||
public FitoTrackDataContainer(){}
|
||||
|
||||
public FitoTrackDataContainer(int version, List<Workout> workouts, List<WorkoutSample> samples, FitoTrackSettings settings) {
|
||||
this.version = version;
|
||||
this.workouts = workouts;
|
||||
this.samples = samples;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public List<Workout> getWorkouts() {
|
||||
return workouts;
|
||||
}
|
||||
|
||||
public void setWorkouts(List<Workout> workouts) {
|
||||
this.workouts = workouts;
|
||||
}
|
||||
|
||||
public List<WorkoutSample> getSamples() {
|
||||
return samples;
|
||||
}
|
||||
|
||||
public void setSamples(List<WorkoutSample> samples) {
|
||||
this.samples = samples;
|
||||
}
|
||||
|
||||
public FitoTrackSettings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
public void setSettings(FitoTrackSettings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
package de.tadris.fitness.util.export;
|
||||
|
||||
public class FitoTrackSettings {
|
||||
|
||||
String preferredUnitSystem;
|
||||
int weight;
|
||||
|
||||
public FitoTrackSettings(){}
|
||||
|
||||
public FitoTrackSettings(String preferredUnitSystem, int weight) {
|
||||
this.preferredUnitSystem = preferredUnitSystem;
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public String getPreferredUnitSystem() {
|
||||
return preferredUnitSystem;
|
||||
}
|
||||
|
||||
public void setPreferredUnitSystem(String preferredUnitSystem) {
|
||||
this.preferredUnitSystem = preferredUnitSystem;
|
||||
}
|
||||
|
||||
public int getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public void setWeight(int weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.gpx;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
@ -18,7 +37,7 @@ public class Gpx {
|
||||
Metadata metadata;
|
||||
|
||||
String name;
|
||||
String desc;
|
||||
private String desc;
|
||||
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
List<Track> trk;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -19,6 +19,7 @@
|
||||
|
||||
package de.tadris.fitness.util.gpx;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
@ -40,7 +41,7 @@ public class GpxExporter {
|
||||
mapper.writeValue(file, getGpxFromWorkout(context, workout));
|
||||
}
|
||||
|
||||
public static Gpx getGpxFromWorkout(Context context, Workout workout){
|
||||
private static Gpx getGpxFromWorkout(Context context, Workout workout) {
|
||||
Gpx gpx= new Gpx();
|
||||
gpx.name= workout.toString();
|
||||
gpx.version= "1.1";
|
||||
@ -52,7 +53,7 @@ public class GpxExporter {
|
||||
return gpx;
|
||||
}
|
||||
|
||||
public static Track getTrackFromWorkout(Context context, Workout workout, int number){
|
||||
private static Track getTrackFromWorkout(Context context, Workout workout, int number) {
|
||||
WorkoutSample[] samples= Instance.getInstance(context).db.workoutDao().getAllSamplesOfWorkout(workout.id);
|
||||
Track track= new Track();
|
||||
track.number= number;
|
||||
@ -60,7 +61,7 @@ public class GpxExporter {
|
||||
track.cmt= workout.comment;
|
||||
track.desc= workout.comment;
|
||||
track.src= "FitoTrack";
|
||||
track.type= workout.workoutType;
|
||||
track.type = workout.getWorkoutType().id;
|
||||
track.trkseg= new ArrayList<>();
|
||||
|
||||
TrackSegment segment= new TrackSegment();
|
||||
@ -77,13 +78,14 @@ public class GpxExporter {
|
||||
return track;
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
private static final SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
|
||||
|
||||
public static String getDateTime(long time){
|
||||
private static String getDateTime(long time) {
|
||||
return getDateTime(new Date(time));
|
||||
}
|
||||
|
||||
public static String getDateTime(Date date){
|
||||
private static String getDateTime(Date date) {
|
||||
return formatter.format(date);
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.gpx;
|
||||
|
||||
public class Metadata {
|
||||
|
||||
String name;
|
||||
String desc;
|
||||
String time;
|
||||
private String name;
|
||||
private String desc;
|
||||
private String time;
|
||||
|
||||
public Metadata() {
|
||||
}
|
||||
|
||||
@ -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.util.gpx;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
|
||||
@ -1,22 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.gpx;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
|
||||
public class TrackPoint {
|
||||
class TrackPoint {
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true)
|
||||
private
|
||||
double lat;
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true)
|
||||
private
|
||||
double lon;
|
||||
|
||||
double ele;
|
||||
private double ele;
|
||||
|
||||
String time;
|
||||
private String time;
|
||||
|
||||
String fix;
|
||||
private String fix;
|
||||
|
||||
TrackPointExtension extensions;
|
||||
private TrackPointExtension extensions;
|
||||
|
||||
public TrackPoint(){}
|
||||
|
||||
|
||||
@ -1,8 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.gpx;
|
||||
|
||||
public class TrackPointExtension {
|
||||
|
||||
double speed;
|
||||
private double speed;
|
||||
|
||||
public TrackPointExtension(){}
|
||||
|
||||
|
||||
@ -1,10 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2020 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.gpx;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TrackSegment {
|
||||
class TrackSegment {
|
||||
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
List<TrackPoint> trkpt;
|
||||
|
||||
@ -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.util.unit;
|
||||
|
||||
public class Imperial implements Unit {
|
||||
|
||||
@ -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.util.unit;
|
||||
|
||||
public class ImperialWithMeters extends Imperial {
|
||||
@ -11,13 +30,9 @@ public class ImperialWithMeters extends Imperial {
|
||||
public double getDistanceFromMeters(double meters) {
|
||||
return meters;
|
||||
}
|
||||
@Override
|
||||
public String getLongDistanceUnit() {
|
||||
return "m";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortDistanceUnit() {
|
||||
return "yd";
|
||||
return "m";
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.util.unit;
|
||||
|
||||
public class Metric implements Unit{
|
||||
|
||||
@ -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.util.unit;
|
||||
|
||||
public class MetricPhysical extends Metric{
|
||||
|
||||
@ -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.util.unit;
|
||||
|
||||
public interface Unit {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -24,22 +24,23 @@ import android.preference.PreferenceManager;
|
||||
|
||||
public class UnitUtils {
|
||||
|
||||
public static final Unit UNITS_METRIC= new Metric();
|
||||
public static final Unit UNITS_METRIC_PHYSICAL= new MetricPhysical();
|
||||
public static final Unit UNITS_IMPERIAL_YARDS= new Imperial();
|
||||
public static final Unit UNITS_IMPERIAL_METERS= new ImperialWithMeters();
|
||||
public static final Unit[] supportedUnits= new Unit[] {
|
||||
private static final Unit UNITS_METRIC = new Metric();
|
||||
private static final Unit UNITS_METRIC_PHYSICAL = new MetricPhysical();
|
||||
private static final Unit UNITS_IMPERIAL_YARDS = new Imperial();
|
||||
private static final Unit UNITS_IMPERIAL_METERS = new ImperialWithMeters();
|
||||
private static final Unit[] supportedUnits = new Unit[]{
|
||||
UNITS_METRIC, UNITS_METRIC_PHYSICAL, UNITS_IMPERIAL_YARDS, UNITS_IMPERIAL_METERS
|
||||
};
|
||||
|
||||
public static Unit CHOSEN_SYSTEM= UNITS_METRIC;
|
||||
|
||||
public static void setUnit(Context context){
|
||||
int id= Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(context).getString("unitSystem", String.valueOf(UnitUtils.UNITS_METRIC.getId())));
|
||||
setUnit(id);
|
||||
String id = PreferenceManager.getDefaultSharedPreferences(context).getString("unitSystem", String.valueOf(UnitUtils.UNITS_METRIC.getId()));
|
||||
assert id != null;
|
||||
setUnit(Integer.parseInt(id));
|
||||
}
|
||||
|
||||
public static void setUnit(int id){
|
||||
private static void setUnit(int id) {
|
||||
CHOSEN_SYSTEM= UNITS_METRIC;
|
||||
for(Unit unit : supportedUnits){
|
||||
if(id == unit.getId()){
|
||||
@ -62,47 +63,38 @@ public class UnitUtils {
|
||||
}
|
||||
|
||||
public static String getHourMinuteSecondTime(long time){
|
||||
long totalSeks= time / 1000;
|
||||
long totalMins= totalSeks / 60;
|
||||
long totalSecs = time / 1000;
|
||||
long totalMins = totalSecs / 60;
|
||||
long hours= totalMins / 60;
|
||||
long mins= totalMins % 60;
|
||||
long seks= totalSeks % 60;
|
||||
long secs = totalSecs % 60;
|
||||
String minStr= (mins < 10 ? "0" : "") + mins;
|
||||
String sekStr= (seks < 10 ? "0" : "") + seks;
|
||||
String sekStr = (secs < 10 ? "0" : "") + secs;
|
||||
return hours + ":" + minStr + ":" + sekStr;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param pace Pace in min/km
|
||||
* @return Pace
|
||||
*/
|
||||
public static String getPace(double pace){
|
||||
public static String getPace(double metricPace) {
|
||||
double one= CHOSEN_SYSTEM.getDistanceFromKilometers(1);
|
||||
return round(pace / one, 1) + " min/" + CHOSEN_SYSTEM.getLongDistanceUnit();
|
||||
double secondsTotal = 60 * metricPace / one;
|
||||
int minutes = (int) secondsTotal / 60;
|
||||
int seconds = (int) secondsTotal % 60;
|
||||
return minutes + ":" + (seconds < 10 ? "0" : "") + seconds + " min/" + CHOSEN_SYSTEM.getLongDistanceUnit();
|
||||
}
|
||||
|
||||
/**
|
||||
*CHOSEN_SYSTEM.getLongDistanceUnit()
|
||||
* @param consumption consumption in kcal/km
|
||||
* @return
|
||||
*/
|
||||
public static String getRelativeEnergyConsumption(double consumption){
|
||||
double one= CHOSEN_SYSTEM.getDistanceFromKilometers(1);
|
||||
return round(consumption / one, 2) + " kcal/" + CHOSEN_SYSTEM.getLongDistanceUnit();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param distance Distance in meters
|
||||
* @return String in preferred unit
|
||||
*/
|
||||
public static String getDistance(int distance){
|
||||
double units= CHOSEN_SYSTEM.getDistanceFromMeters(distance);
|
||||
if(units >= 1000){
|
||||
return round(units / 1000, 1) + " " + CHOSEN_SYSTEM.getLongDistanceUnit();
|
||||
public static String getDistance(int distanceInMeters) {
|
||||
if (distanceInMeters >= 1000) {
|
||||
return round(CHOSEN_SYSTEM.getDistanceFromKilometers((double) distanceInMeters / 1000d), 1) + " " + CHOSEN_SYSTEM.getLongDistanceUnit();
|
||||
}else{
|
||||
return (int)units + " " + CHOSEN_SYSTEM.getShortDistanceUnit();
|
||||
return (int) CHOSEN_SYSTEM.getDistanceFromMeters(distanceInMeters) + " " + CHOSEN_SYSTEM.getShortDistanceUnit();
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,7 +107,7 @@ public class UnitUtils {
|
||||
return round(CHOSEN_SYSTEM.getSpeedFromMeterPerSecond(speed), 1) + " " + CHOSEN_SYSTEM.getSpeedUnit();
|
||||
}
|
||||
|
||||
public static double round(double d, int count){
|
||||
private static double round(double d, int count) {
|
||||
return (double)Math.round(d * Math.pow(10, count)) / Math.pow(10, count);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -28,8 +28,8 @@ import de.tadris.fitness.R;
|
||||
|
||||
public class ProgressDialogController {
|
||||
|
||||
private Activity context;
|
||||
private Dialog dialog;
|
||||
private final Activity context;
|
||||
private final Dialog dialog;
|
||||
private TextView infoView;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
@ -38,7 +38,7 @@ public class ProgressDialogController {
|
||||
setTitle(title);
|
||||
}
|
||||
|
||||
public ProgressDialogController(Activity context) {
|
||||
private ProgressDialogController(Activity context) {
|
||||
this.context = context;
|
||||
this.dialog= new Dialog(context);
|
||||
initDialog();
|
||||
@ -51,7 +51,7 @@ public class ProgressDialogController {
|
||||
progressBar= dialog.findViewById(R.id.dialogProgressBar);
|
||||
}
|
||||
|
||||
public void setTitle(String title){
|
||||
private void setTitle(String title) {
|
||||
dialog.setTitle(title);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
* Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
*
|
||||
* This file is part of FitoTrack
|
||||
*
|
||||
@ -32,7 +32,6 @@ import java.util.Date;
|
||||
|
||||
import de.tadris.fitness.R;
|
||||
import de.tadris.fitness.data.Workout;
|
||||
import de.tadris.fitness.util.WorkoutTypeCalculator;
|
||||
import de.tadris.fitness.util.unit.UnitUtils;
|
||||
|
||||
public class WorkoutAdapter extends RecyclerView.Adapter<WorkoutAdapter.WorkoutViewHolder>{
|
||||
@ -40,10 +39,14 @@ public class WorkoutAdapter extends RecyclerView.Adapter<WorkoutAdapter.WorkoutV
|
||||
|
||||
public static class WorkoutViewHolder extends RecyclerView.ViewHolder{
|
||||
|
||||
View root;
|
||||
TextView lengthText, timeText, dateText, typeText, commentText;
|
||||
final View root;
|
||||
final TextView lengthText;
|
||||
final TextView timeText;
|
||||
final TextView dateText;
|
||||
final TextView typeText;
|
||||
final TextView commentText;
|
||||
|
||||
public WorkoutViewHolder(@NonNull View itemView) {
|
||||
WorkoutViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
this.root= itemView;
|
||||
lengthText= itemView.findViewById(R.id.workoutLength);
|
||||
@ -54,8 +57,8 @@ public class WorkoutAdapter extends RecyclerView.Adapter<WorkoutAdapter.WorkoutV
|
||||
}
|
||||
}
|
||||
|
||||
Workout[] workouts;
|
||||
WorkoutAdapterListener listener;
|
||||
private final Workout[] workouts;
|
||||
private final WorkoutAdapterListener listener;
|
||||
|
||||
public WorkoutAdapter(Workout[] workouts, WorkoutAdapterListener listener) {
|
||||
this.workouts = workouts;
|
||||
@ -74,7 +77,7 @@ public class WorkoutAdapter extends RecyclerView.Adapter<WorkoutAdapter.WorkoutV
|
||||
public void onBindViewHolder(WorkoutViewHolder holder, final int position) {
|
||||
Workout workout= workouts[position];
|
||||
holder.dateText.setText(SimpleDateFormat.getDateTimeInstance().format(new Date(workout.start)));
|
||||
holder.typeText.setText(WorkoutTypeCalculator.getType(workout));
|
||||
holder.typeText.setText(workout.getWorkoutType().title);
|
||||
if(workout.comment != null){
|
||||
if(workout.comment.length() > 33){
|
||||
holder.commentText.setText(workout.comment.substring(0, 30) + "...");
|
||||
@ -86,7 +89,11 @@ public class WorkoutAdapter extends RecyclerView.Adapter<WorkoutAdapter.WorkoutV
|
||||
}
|
||||
holder.lengthText.setText(UnitUtils.getDistance(workout.length));
|
||||
holder.timeText.setText(UnitUtils.getHourMinuteTime(workout.duration));
|
||||
holder.root.setOnClickListener(v -> listener.onItemClick(workout));
|
||||
holder.root.setOnClickListener(v -> listener.onItemClick(position, workout));
|
||||
holder.root.setOnLongClickListener(v -> {
|
||||
listener.onItemLongClick(position, workout);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Return the size of your dataset (invoked by the layout manager)
|
||||
@ -96,7 +103,8 @@ public class WorkoutAdapter extends RecyclerView.Adapter<WorkoutAdapter.WorkoutV
|
||||
}
|
||||
|
||||
public interface WorkoutAdapterListener{
|
||||
void onItemClick(Workout workout);
|
||||
void onItemClick(int pos, Workout workout);
|
||||
void onItemLongClick(int pos, Workout workout);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
~
|
||||
~ This file is part of FitoTrack
|
||||
~
|
||||
@ -20,5 +19,7 @@
|
||||
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="300">
|
||||
<alpha android:fromAlpha="0" android:toAlpha="1"/>
|
||||
<alpha
|
||||
android:fromAlpha="0"
|
||||
android:toAlpha="1" />
|
||||
</set>
|
||||
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
~
|
||||
~ This file is part of FitoTrack
|
||||
~
|
||||
|
||||
@ -1,10 +1,29 @@
|
||||
<!--
|
||||
~ Copyright (c) 2020 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:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#FFFFFF">
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||
</vector>
|
||||
|
||||
30
app/src/main/res/drawable-anydpi/ic_gps_no_fix.xml
Normal file
30
app/src/main/res/drawable-anydpi/ic_gps_no_fix.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<!--
|
||||
~ Copyright (c) 2020 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:alpha="0.8"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94V1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11H1v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94V23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94H23v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z" />
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable-hdpi/ic_gps_no_fix.png
Normal file
BIN
app/src/main/res/drawable-hdpi/ic_gps_no_fix.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 358 B |
BIN
app/src/main/res/drawable-mdpi/ic_gps_no_fix.png
Normal file
BIN
app/src/main/res/drawable-mdpi/ic_gps_no_fix.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 247 B |
BIN
app/src/main/res/drawable-xhdpi/ic_gps_no_fix.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/ic_gps_no_fix.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 439 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_gps_no_fix.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_gps_no_fix.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 670 B |
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
~
|
||||
~ This file is part of FitoTrack
|
||||
~
|
||||
@ -18,11 +18,11 @@
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
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"/>
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0" />
|
||||
</vector>
|
||||
|
||||
32
app/src/main/res/layout/activity_enter_workout.xml
Normal file
32
app/src/main/res/layout/activity_enter_workout.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
~
|
||||
~ This file is part of FitoTrack
|
||||
~
|
||||
~ FitoTrack is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ FitoTrack is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".activity.EnterWorkoutActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/enterWorkoutRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="15dp" />
|
||||
</ScrollView>
|
||||
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
~
|
||||
~ This file is part of FitoTrack
|
||||
~
|
||||
@ -19,10 +18,10 @@
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context=".activity.ListWorkoutsActivity">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
@ -36,12 +35,12 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|right"
|
||||
android:layout_margin="10dp"
|
||||
app:menu_animationDelayPerItem="50"
|
||||
app:menu_colorNormal="@color/colorPrimary"
|
||||
app:menu_colorPressed="@color/colorPrimaryDark"
|
||||
app:menu_animationDelayPerItem="50"
|
||||
app:menu_icon="@drawable/fab_add"
|
||||
app:menu_labels_showAnimation="@anim/fab_slide_in_from_right"
|
||||
app:menu_labels_hideAnimation="@anim/fab_slide_out_to_right">
|
||||
app:menu_labels_hideAnimation="@anim/fab_slide_out_to_right"
|
||||
app:menu_labels_showAnimation="@anim/fab_slide_in_from_right">
|
||||
|
||||
<com.github.clans.fab.FloatingActionButton
|
||||
android:id="@+id/workoutListRecordRunning"
|
||||
@ -51,7 +50,7 @@
|
||||
app:fab_colorNormal="@color/colorPrimaryRunning"
|
||||
app:fab_colorPressed="@color/colorPrimaryDarkRunning"
|
||||
app:fab_label="@string/workoutTypeRunning"
|
||||
app:fab_size="normal"/>
|
||||
app:fab_size="normal" />
|
||||
|
||||
<com.github.clans.fab.FloatingActionButton
|
||||
android:id="@+id/workoutListRecordHiking"
|
||||
@ -71,7 +70,17 @@
|
||||
app:fab_colorNormal="@color/colorPrimaryBicycling"
|
||||
app:fab_colorPressed="@color/colorPrimaryDarkBicycling"
|
||||
app:fab_label="@string/workoutTypeCycling"
|
||||
app:fab_size="normal"/>
|
||||
app:fab_size="normal" />
|
||||
|
||||
<com.github.clans.fab.FloatingActionButton
|
||||
android:id="@+id/workoutListEnter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_add_white"
|
||||
app:fab_colorNormal="@color/colorPrimary"
|
||||
app:fab_colorPressed="@color/colorPrimaryDark"
|
||||
app:fab_label="@string/enterWorkout"
|
||||
app:fab_size="normal" />
|
||||
|
||||
</com.github.clans.fab.FloatingActionMenu>
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
~
|
||||
~ This file is part of FitoTrack
|
||||
~
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
~
|
||||
~ This file is part of FitoTrack
|
||||
~
|
||||
@ -32,7 +31,7 @@
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/recordMapViewrRoot"
|
||||
android:id="@+id/recordMapViewerRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
@ -56,6 +55,15 @@
|
||||
android:text="@string/gps"
|
||||
android:textColor="@android:color/transparent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recordMapAttribution"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center"
|
||||
android:layout_margin="5dp"
|
||||
android:textColor="@color/textColorLight"
|
||||
android:text="@string/OpenStreetMapAttribution" />
|
||||
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@ -65,8 +73,8 @@
|
||||
android:layout_height="200dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:padding="10dp"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/recordTime"
|
||||
@ -75,7 +83,7 @@
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:text="0:44:08"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/black"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
@ -117,7 +125,7 @@
|
||||
android:text="2,06 km"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@android:color/black"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
@ -137,7 +145,7 @@
|
||||
android:text="30 kcal"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@android:color/black"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
@ -156,7 +164,7 @@
|
||||
android:id="@+id/recordInfo3Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/workoutAvgSpeed"
|
||||
android:text="@string/workoutAvgSpeedShort"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold" />
|
||||
@ -168,7 +176,7 @@
|
||||
android:text="7 km/h"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@android:color/black"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
@ -188,7 +196,7 @@
|
||||
android:text="30 kcal"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@android:color/black"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
@ -197,4 +205,34 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/recorderWaitingOverlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/holo_red_light">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:src="@drawable/ic_gps_no_fix" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="20dp"
|
||||
android:text="@string/waiting_gps"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@android:color/background_light"
|
||||
android:textSize="30sp" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de>
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
~
|
||||
~ This file is part of FitoTrack
|
||||
~
|
||||
@ -18,16 +17,25 @@
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/showWorkoutSceneRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".activity.ShowWorkoutActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/showWorkoutRoot"
|
||||
<ScrollView
|
||||
android:id="@+id/showWorkoutDefaultSceneRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp"></LinearLayout>
|
||||
</ScrollView>
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/showWorkoutRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp" />
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
36
app/src/main/res/layout/activity_show_workout_map.xml
Normal file
36
app/src/main/res/layout/activity_show_workout_map.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
~
|
||||
~ This file is part of FitoTrack
|
||||
~
|
||||
~ FitoTrack is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ FitoTrack is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/showWorkoutMapRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".activity.ShowWorkoutMapActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/showWorkoutMapParent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
~
|
||||
~ This file is part of FitoTrack
|
||||
~
|
||||
~ FitoTrack is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ FitoTrack is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/showWorkoutMapRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".activity.ShowWorkoutMapActivity">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline4"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintGuide_percent="0.6"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/showWorkoutMapParent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toTopOf="@+id/guideline4"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/showWorkoutDiagramParent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/guideline4" />
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
54
app/src/main/res/layout/dialog_duration_picker.xml
Normal file
54
app/src/main/res/layout/dialog_duration_picker.xml
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
~
|
||||
~ This file is part of FitoTrack
|
||||
~
|
||||
~ FitoTrack is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ FitoTrack is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<NumberPicker
|
||||
android:id="@+id/durationPickerHours"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="30dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=":" />
|
||||
|
||||
<NumberPicker
|
||||
android:id="@+id/durationPickerMinutes"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="30dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=":" />
|
||||
|
||||
<NumberPicker
|
||||
android:id="@+id/durationPickerSeconds"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="30dp" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -1,4 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de>
|
||||
~
|
||||
~ This file is part of FitoTrack
|
||||
~
|
||||
~ FitoTrack is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ FitoTrack is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user