mirror of
				https://github.com/russok/FitoTrack.git
				synced 2025-10-30 09:12:11 -07:00 
			
		
		
		
	Compare commits
	
		
			92 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 | ||
|   | 07b362ddcc | 
							
								
								
									
										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 | ||||||
|  | 
 | ||||||
							
								
								
									
										24
									
								
								NOTICE.md
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								NOTICE.md
									
									
									
									
									
								
							| @ -49,6 +49,30 @@ | |||||||
|     See the License for the specific language governing permissions and |     See the License for the specific language governing permissions and | ||||||
|     limitations under the License. |     limitations under the License. | ||||||
| 
 | 
 | ||||||
|  | <https://github.com/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> | <https://github.com/westnordost/osmapi> | ||||||
| 
 | 
 | ||||||
|     © 2016-2019 Tobias Zwick. This library is released under the terms of the GNU Lesser General Public License (LGPL). |     © 2016-2019 Tobias Zwick. This library is released under the terms of the GNU Lesser General Public License (LGPL). | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ FitoTrack is a mobile app for logging and viewing your workouts. Whether you're | |||||||
| - **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. | - **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. | - **Open-Source.** There is neither advertivesment nor tracking, and the source code is open and licensed under the GPLv3. | ||||||
| 
 | 
 | ||||||
| see the [Feature-List](https://codeberg.org/jannis/FitoTrack/wiki/Features) for a detailed list of features and the [Roadmap](https://codeberg.org/jannis/FitoTrack/wiki/Roadmap) for planned ones. | Please see the [Userguide](https://codeberg.org/jannis/FitoTrack/wiki/How-to-use) if you have any problems. | ||||||
| 
 | 
 | ||||||
| ## Contributing | ## Contributing | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -35,8 +35,8 @@ android { | |||||||
|         applicationId "de.tadris.fitness" |         applicationId "de.tadris.fitness" | ||||||
|         minSdkVersion 21 |         minSdkVersion 21 | ||||||
|         targetSdkVersion 28 |         targetSdkVersion 28 | ||||||
|         versionCode 300 |         versionCode 500 | ||||||
|         versionName "3.0" |         versionName "5.0" | ||||||
|         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" |         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | ||||||
|     } |     } | ||||||
|     buildTypes { |     buildTypes { | ||||||
| @ -49,6 +49,12 @@ android { | |||||||
|         sourceCompatibility = '1.8' |         sourceCompatibility = '1.8' | ||||||
|         targetCompatibility = '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 { | dependencies { | ||||||
| @ -67,7 +73,7 @@ dependencies { | |||||||
|     implementation 'org.mapsforge:mapsforge-map-android:0.11.0' |     implementation 'org.mapsforge:mapsforge-map-android:0.11.0' | ||||||
|     implementation 'com.caverock:androidsvg:1.3' |     implementation 'com.caverock:androidsvg:1.3' | ||||||
| 
 | 
 | ||||||
|     // Charts |     // UI | ||||||
|     implementation 'net.sf.kxml:kxml2:2.3.0' |     implementation 'net.sf.kxml:kxml2:2.3.0' | ||||||
|     implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' |     implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' | ||||||
|     implementation 'com.github.clans:fab:1.6.4' |     implementation 'com.github.clans:fab:1.6.4' | ||||||
| @ -76,8 +82,11 @@ dependencies { | |||||||
|     implementation 'stax:stax-api:1.0.1' |     implementation 'stax:stax-api:1.0.1' | ||||||
|     implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8' |     implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.8' | ||||||
| 
 | 
 | ||||||
|  |     // File Utils | ||||||
|  |     implementation 'commons-io:commons-io:2.6' | ||||||
|  | 
 | ||||||
|     // Upload to OSM |     // Upload to OSM | ||||||
|     implementation ('de.westnordost:osmapi-traces:1.0') |     implementation('de.westnordost:osmapi-traces:1.0') | ||||||
|     configurations { |     configurations { | ||||||
|         compile.exclude group: 'net.sf.kxml', module: 'kxml2' // already included in Android |         compile.exclude group: 'net.sf.kxml', module: 'kxml2' // already included in Android | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <!-- | <!-- | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
| @ -23,32 +23,38 @@ | |||||||
|     package="de.tadris.fitness"> |     package="de.tadris.fitness"> | ||||||
| 
 | 
 | ||||||
|     <uses-permission android:name="android.permission.ACCESS_FINE_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.INTERNET" /> | ||||||
|     <uses-permission android:name="android.permission.WAKE_LOCK" /> |     <uses-permission android:name="android.permission.WAKE_LOCK" /> | ||||||
|     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> |     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> | ||||||
|     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> |     <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" /> |     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> | ||||||
| 
 | 
 | ||||||
|     <application |     <application | ||||||
|         android:allowBackup="true" |         android:allowBackup="true" | ||||||
|  |         android:appCategory="productivity" | ||||||
|         android:icon="@mipmap/ic_launcher" |         android:icon="@mipmap/ic_launcher" | ||||||
|         android:label="@string/app_name" |         android:label="@string/app_name" | ||||||
|         android:roundIcon="@mipmap/ic_launcher_round" |         android:roundIcon="@mipmap/ic_launcher_round" | ||||||
|         android:supportsRtl="true" |         android:supportsRtl="true" | ||||||
|         android:theme="@style/AppTheme" |         android:theme="@style/AppTheme" | ||||||
|         android:appCategory="productivity" |  | ||||||
|         tools:ignore="GoogleAppIndexingWarning"> |         tools:ignore="GoogleAppIndexingWarning"> | ||||||
|         <activity android:name=".activity.ShowWorkoutMapActivity" |         <activity android:name=".activity.VoiceAnnouncementsSettingsActivity" /> | ||||||
|             android:screenOrientation="portrait"></activity> |  | ||||||
|         <activity android:name=".activity.ShowWorkoutMapDiagramActivity" |  | ||||||
|             android:screenOrientation="portrait"></activity> |  | ||||||
|         <activity android:name=".activity.SettingsActivity" /> |         <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 |         <activity | ||||||
|             android:name=".activity.ShowWorkoutActivity" |             android:name=".activity.ShowWorkoutActivity" | ||||||
|             android:screenOrientation="portrait" /> |             android:screenOrientation="portrait" /> | ||||||
|         <activity |         <activity | ||||||
|             android:name=".activity.RecordWorkoutActivity" |             android:name=".activity.RecordWorkoutActivity" | ||||||
|  |             android:showOnLockScreen="true" | ||||||
|             android:screenOrientation="portrait" /> |             android:screenOrientation="portrait" /> | ||||||
|         <activity |         <activity | ||||||
|             android:name=".activity.ListWorkoutsActivity" |             android:name=".activity.ListWorkoutsActivity" | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -32,11 +32,12 @@ import java.util.List; | |||||||
| import de.tadris.fitness.data.AppDatabase; | import de.tadris.fitness.data.AppDatabase; | ||||||
| import de.tadris.fitness.data.UserPreferences; | import de.tadris.fitness.data.UserPreferences; | ||||||
| import de.tadris.fitness.recording.LocationListener; | import de.tadris.fitness.recording.LocationListener; | ||||||
|  | import de.tadris.fitness.util.FitoTrackThemes; | ||||||
| import de.tadris.fitness.util.unit.UnitUtils; | import de.tadris.fitness.util.unit.UnitUtils; | ||||||
| 
 | 
 | ||||||
| public class Instance { | public class Instance { | ||||||
| 
 | 
 | ||||||
|     public static final String DATABASE_NAME= "fito-track"; |     private static final String DATABASE_NAME = "fito-track"; | ||||||
| 
 | 
 | ||||||
|     private static Instance instance; |     private static Instance instance; | ||||||
| 
 | 
 | ||||||
| @ -47,20 +48,22 @@ public class Instance { | |||||||
|         return instance; |         return instance; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public AppDatabase db; |     public final AppDatabase db; | ||||||
|     public List<LocationListener.LocationChangeListener> locationChangeListeners= new ArrayList<>(); |     public final List<LocationListener.LocationChangeListener> locationChangeListeners = new ArrayList<>(); | ||||||
|     public UserPreferences userPreferences; |     public final UserPreferences userPreferences; | ||||||
|  |     public final FitoTrackThemes themes; | ||||||
| 
 | 
 | ||||||
|     public boolean pressureAvailable= false; |     public boolean pressureAvailable= false; | ||||||
|     public float lastPressure= 0; |     public float lastPressure= 0; | ||||||
| 
 | 
 | ||||||
|     private Instance(Context context) { |     private Instance(Context context) { | ||||||
|         userPreferences= new UserPreferences(context); |         userPreferences= new UserPreferences(context); | ||||||
|  |         themes = new FitoTrackThemes(context); | ||||||
|         db = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME) |         db = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME) | ||||||
|                 .addMigrations(new Migration(1, 2) { |                 .addMigrations(new Migration(1, 2) { | ||||||
|                     @Override |                     @Override | ||||||
|                     public void migrate(@NonNull SupportSQLiteDatabase database) { |                     public void migrate(@NonNull SupportSQLiteDatabase database) { | ||||||
|                         try{ |                         try { | ||||||
|                             database.beginTransaction(); |                             database.beginTransaction(); | ||||||
| 
 | 
 | ||||||
|                             database.execSQL("ALTER table workout add descent REAL NOT NULL DEFAULT 0;"); |                             database.execSQL("ALTER table workout add descent REAL NOT NULL DEFAULT 0;"); | ||||||
| @ -85,7 +88,20 @@ public class Instance { | |||||||
|                             database.execSQL("DROP TABLE workout_sample2"); |                             database.execSQL("DROP TABLE workout_sample2"); | ||||||
| 
 | 
 | ||||||
|                             database.setTransactionSuccessful(); |                             database.setTransactionSuccessful(); | ||||||
|                         }finally { |                         } 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(); |                             database.endTransaction(); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -19,48 +19,35 @@ | |||||||
| 
 | 
 | ||||||
| package de.tadris.fitness.activity; | package de.tadris.fitness.activity; | ||||||
| 
 | 
 | ||||||
|  | import android.Manifest; | ||||||
| import android.app.Activity; | import android.app.Activity; | ||||||
| import android.app.AlertDialog; | import android.app.AlertDialog; | ||||||
| import android.content.Intent; | import android.content.pm.PackageManager; | ||||||
| import android.net.Uri; | import android.os.Bundle; | ||||||
| import android.util.Log; |  | ||||||
| import android.util.TypedValue; | import android.util.TypedValue; | ||||||
| 
 | 
 | ||||||
|  | import androidx.annotation.Nullable; | ||||||
| import androidx.annotation.StringRes; | import androidx.annotation.StringRes; | ||||||
|  | import androidx.core.app.ActivityCompat; | ||||||
| 
 | 
 | ||||||
| import java.io.BufferedInputStream; | import de.tadris.fitness.Instance; | ||||||
| import java.io.FileNotFoundException; |  | ||||||
| 
 |  | ||||||
| import de.tadris.fitness.R; | import de.tadris.fitness.R; | ||||||
| 
 | 
 | ||||||
| abstract public class FitoTrackActivity extends Activity { | abstract public class FitoTrackActivity extends Activity { | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onCreate(@Nullable Bundle savedInstanceState) { | ||||||
|  |         super.onCreate(savedInstanceState); | ||||||
|  |         setTheme(Instance.getInstance(this).themes.getDefaultTheme()); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 
 |     int getThemePrimaryColor() { | ||||||
|     protected int getThemePrimaryColor() { |  | ||||||
|         final TypedValue value = new TypedValue (); |         final TypedValue value = new TypedValue (); | ||||||
|         getTheme().resolveAttribute (android.R.attr.colorPrimary, value, true); |         getTheme().resolveAttribute (android.R.attr.colorPrimary, value, true); | ||||||
|         return value.data; |         return value.data; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected void shareFile(Uri uri){ |     void showErrorDialog(Exception e, @StringRes int title, @StringRes int message) { | ||||||
|         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) |         new AlertDialog.Builder(this) | ||||||
|                 .setTitle(title) |                 .setTitle(title) | ||||||
|                 .setMessage(getString(message) + "\n\n" + e.getMessage()) |                 .setMessage(getString(message) + "\n\n" + e.getMessage()) | ||||||
| @ -68,5 +55,16 @@ abstract public class FitoTrackActivity extends Activity { | |||||||
|                 .create().show(); |                 .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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -42,12 +42,12 @@ public class LauncherActivity extends Activity { | |||||||
|         new Handler().postDelayed(this::init, 100); |         new Handler().postDelayed(this::init, 100); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void init(){ |     private void init() { | ||||||
|         Instance.getInstance(this); |         Instance.getInstance(this); | ||||||
|         start(); |         start(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void start(){ |     private void start() { | ||||||
|         startActivity(new Intent(this, ListWorkoutsActivity.class)); |         startActivity(new Intent(this, ListWorkoutsActivity.class)); | ||||||
|         finish(); |         finish(); | ||||||
|         overridePendingTransition(R.anim.fade_in, R.anim.stay); |         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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -19,7 +19,6 @@ | |||||||
| 
 | 
 | ||||||
| package de.tadris.fitness.activity; | package de.tadris.fitness.activity; | ||||||
| 
 | 
 | ||||||
| import android.app.Activity; |  | ||||||
| import android.app.AlertDialog; | import android.app.AlertDialog; | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.content.SharedPreferences; | import android.content.SharedPreferences; | ||||||
| @ -37,16 +36,17 @@ import com.github.clans.fab.FloatingActionMenu; | |||||||
| import de.tadris.fitness.Instance; | import de.tadris.fitness.Instance; | ||||||
| import de.tadris.fitness.R; | import de.tadris.fitness.R; | ||||||
| import de.tadris.fitness.data.Workout; | import de.tadris.fitness.data.Workout; | ||||||
|  | import de.tadris.fitness.data.WorkoutType; | ||||||
| import de.tadris.fitness.util.DialogUtils; | import de.tadris.fitness.util.DialogUtils; | ||||||
| import de.tadris.fitness.view.WorkoutAdapter; | 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 listView; | ||||||
|     private RecyclerView.Adapter adapter; |     private RecyclerView.Adapter adapter; | ||||||
|     private RecyclerView.LayoutManager layoutManager; |     private RecyclerView.LayoutManager layoutManager; | ||||||
|     private FloatingActionMenu menu; |     private FloatingActionMenu menu; | ||||||
|     Workout[] workouts; |     private Workout[] workouts; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
| @ -63,23 +63,21 @@ public class ListWorkoutsActivity extends Activity implements WorkoutAdapter.Wor | |||||||
|         menu= findViewById(R.id.workoutListMenu); |         menu= findViewById(R.id.workoutListMenu); | ||||||
|         menu.setOnMenuButtonLongClickListener(v -> { |         menu.setOnMenuButtonLongClickListener(v -> { | ||||||
|             if(workouts.length > 0){ |             if(workouts.length > 0){ | ||||||
|                 startRecording(workouts[0].workoutType); |                 startRecording(workouts[0].getWorkoutType()); | ||||||
|                 return true; |                 return true; | ||||||
|             }else{ |             }else{ | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         findViewById(R.id.workoutListRecordRunning).setOnClickListener(v -> startRecording(Workout.WORKOUT_TYPE_RUNNING)); |         findViewById(R.id.workoutListRecordRunning).setOnClickListener(v -> startRecording(WorkoutType.RUNNING)); | ||||||
|         findViewById(R.id.workoutListRecordHiking) .setOnClickListener(v -> startRecording(Workout.WORKOUT_TYPE_HIKING)); |         findViewById(R.id.workoutListRecordHiking).setOnClickListener(v -> startRecording(WorkoutType.HIKING)); | ||||||
|         findViewById(R.id.workoutListRecordCycling).setOnClickListener(v -> startRecording(Workout.WORKOUT_TYPE_CYCLING)); |         findViewById(R.id.workoutListRecordCycling).setOnClickListener(v -> startRecording(WorkoutType.CYCLING)); | ||||||
| 
 |         findViewById(R.id.workoutListEnter).setOnClickListener(v -> startEnterWorkoutActivity()); | ||||||
|         loadData(); |  | ||||||
| 
 | 
 | ||||||
|         checkFirstStart(); |         checkFirstStart(); | ||||||
| 
 | 
 | ||||||
|         refreshAdapter(); |         refresh(); | ||||||
| 
 |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void checkFirstStart(){ |     private void checkFirstStart(){ | ||||||
| @ -95,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); |         menu.close(true); | ||||||
|         RecordWorkoutActivity.ACTIVITY= activity; |         RecordWorkoutActivity.ACTIVITY= activity; | ||||||
|         final Intent intent= new Intent(this, RecordWorkoutActivity.class); |         final Intent intent= new Intent(this, RecordWorkoutActivity.class); | ||||||
| @ -106,15 +110,11 @@ public class ListWorkoutsActivity extends Activity implements WorkoutAdapter.Wor | |||||||
|     public void onResume() { |     public void onResume() { | ||||||
|         super.onResume(); |         super.onResume(); | ||||||
| 
 | 
 | ||||||
|         int count= workouts.length; |         refresh(); | ||||||
|         loadData(); |  | ||||||
|         if(count != workouts.length){ |  | ||||||
|             refreshAdapter(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void onItemClick(Workout workout) { |     public void onItemClick(int pos, Workout workout) { | ||||||
|         ShowWorkoutActivity.selectedWorkout= workout; |         ShowWorkoutActivity.selectedWorkout= workout; | ||||||
|         startActivity(new Intent(this, ShowWorkoutActivity.class)); |         startActivity(new Intent(this, ShowWorkoutActivity.class)); | ||||||
|     } |     } | ||||||
| @ -123,11 +123,15 @@ public class ListWorkoutsActivity extends Activity implements WorkoutAdapter.Wor | |||||||
|     public void onItemLongClick(int pos, Workout workout) { |     public void onItemLongClick(int pos, Workout workout) { | ||||||
|         DialogUtils.showDeleteWorkoutDialog(this, () -> { |         DialogUtils.showDeleteWorkoutDialog(this, () -> { | ||||||
|             Instance.getInstance(ListWorkoutsActivity.this).db.workoutDao().deleteWorkout(workout); |             Instance.getInstance(ListWorkoutsActivity.this).db.workoutDao().deleteWorkout(workout); | ||||||
|             loadData(); |             refresh(); | ||||||
|             adapter.notifyItemRemoved(pos); |  | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private void refresh() { | ||||||
|  |         loadData(); | ||||||
|  |         refreshAdapter(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private void loadData(){ |     private void loadData(){ | ||||||
|         workouts= Instance.getInstance(this).db.workoutDao().getWorkouts(); |         workouts= Instance.getInstance(this).db.workoutDao().getWorkouts(); | ||||||
|     } |     } | ||||||
| @ -148,8 +152,7 @@ public class ListWorkoutsActivity extends Activity implements WorkoutAdapter.Wor | |||||||
|     public boolean onOptionsItemSelected(MenuItem item) { |     public boolean onOptionsItemSelected(MenuItem item) { | ||||||
|         int id = item.getItemId(); |         int id = item.getItemId(); | ||||||
| 
 | 
 | ||||||
|         switch (id){ |         if (id == R.id.actionOpenSettings) { | ||||||
|             case R.id.actionOpenSettings: |  | ||||||
|             startActivity(new Intent(this, SettingsActivity.class)); |             startActivity(new Intent(this, SettingsActivity.class)); | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -35,6 +35,7 @@ import android.view.Menu; | |||||||
| import android.view.MenuItem; | import android.view.MenuItem; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
|  | import android.view.WindowManager; | ||||||
| import android.widget.EditText; | import android.widget.EditText; | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
| 
 | 
 | ||||||
| @ -53,54 +54,62 @@ import java.util.List; | |||||||
| 
 | 
 | ||||||
| import de.tadris.fitness.Instance; | import de.tadris.fitness.Instance; | ||||||
| import de.tadris.fitness.R; | import de.tadris.fitness.R; | ||||||
| import de.tadris.fitness.data.Workout; | import de.tadris.fitness.data.WorkoutType; | ||||||
| import de.tadris.fitness.map.MapManager; | import de.tadris.fitness.map.MapManager; | ||||||
| import de.tadris.fitness.map.tilesource.TileSources; |  | ||||||
| import de.tadris.fitness.recording.LocationListener; | import de.tadris.fitness.recording.LocationListener; | ||||||
| import de.tadris.fitness.recording.PressureService; | import de.tadris.fitness.recording.PressureService; | ||||||
| import de.tadris.fitness.recording.WorkoutRecorder; | import de.tadris.fitness.recording.WorkoutRecorder; | ||||||
| import de.tadris.fitness.util.ThemeManager; | import de.tadris.fitness.recording.announcement.AnnouncementGPSStatus; | ||||||
|  | import de.tadris.fitness.recording.announcement.VoiceAnnouncements; | ||||||
| import de.tadris.fitness.util.unit.UnitUtils; | import de.tadris.fitness.util.unit.UnitUtils; | ||||||
| 
 | 
 | ||||||
| public class RecordWorkoutActivity extends FitoTrackActivity implements LocationListener.LocationChangeListener, WorkoutRecorder.WorkoutRecorderListener { | 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; |     private MapView mapView; | ||||||
|     TileDownloadLayer downloadLayer; |     private TileDownloadLayer downloadLayer; | ||||||
|     WorkoutRecorder recorder; |     private WorkoutRecorder recorder; | ||||||
|     Polyline polyline; |     private Polyline polyline; | ||||||
|     List<LatLong> latLongList= new ArrayList<>(); |     private final List<LatLong> latLongList = new ArrayList<>(); | ||||||
|     InfoViewHolder[] infoViews= new InfoViewHolder[4]; |     private final InfoViewHolder[] infoViews = new InfoViewHolder[4]; | ||||||
|     TextView timeView, gpsStatusView; |     private TextView timeView; | ||||||
|     View waitingForGPSOverlay; |     private TextView gpsStatusView; | ||||||
|     boolean gpsFound= false; |     private TextView attribution; | ||||||
|     boolean isResumed= false; |     private View waitingForGPSOverlay; | ||||||
|     private Handler mHandler= new Handler(); |     private boolean gpsFound = false; | ||||||
|     PowerManager.WakeLock wakeLock; |     private boolean isResumed = false; | ||||||
|     Intent locationListener; |     private final Handler mHandler = new Handler(); | ||||||
|     Intent pressureService; |     private PowerManager.WakeLock wakeLock; | ||||||
|  |     private Intent locationListener; | ||||||
|  |     private Intent pressureService; | ||||||
|     private boolean saved= false; |     private boolean saved= false; | ||||||
| 
 | 
 | ||||||
|  |     private VoiceAnnouncements voiceAnnouncements; | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected void onCreate(Bundle savedInstanceState) { |     protected void onCreate(Bundle savedInstanceState) { | ||||||
|         super.onCreate(savedInstanceState); |         super.onCreate(savedInstanceState); | ||||||
|         setTheme(ThemeManager.getThemeByWorkoutType(ACTIVITY)); |         setTheme(Instance.getInstance(this).themes.getWorkoutTypeTheme(ACTIVITY)); | ||||||
|         setContentView(R.layout.activity_record_workout); |         setContentView(R.layout.activity_record_workout); | ||||||
| 
 | 
 | ||||||
|         setTitle(R.string.recordWorkout); |         setTitle(R.string.recordWorkout); | ||||||
| 
 | 
 | ||||||
|         setupMap(); |         setupMap(); | ||||||
| 
 | 
 | ||||||
|         ((ViewGroup)findViewById(R.id.recordMapViewrRoot)).addView(mapView); |         ((ViewGroup) findViewById(R.id.recordMapViewerRoot)).addView(mapView); | ||||||
|         waitingForGPSOverlay= findViewById(R.id.recorderWaitingOverlay); |         waitingForGPSOverlay= findViewById(R.id.recorderWaitingOverlay); | ||||||
|         waitingForGPSOverlay.setVisibility(View.VISIBLE); |         waitingForGPSOverlay.setVisibility(View.VISIBLE); | ||||||
| 
 | 
 | ||||||
|  |         attribution = findViewById(R.id.recordMapAttribution); | ||||||
|  | 
 | ||||||
|         checkPermissions(); |         checkPermissions(); | ||||||
| 
 | 
 | ||||||
|         recorder= new WorkoutRecorder(this, ACTIVITY, this); |         recorder= new WorkoutRecorder(this, ACTIVITY, this); | ||||||
|         recorder.start(); |         recorder.start(); | ||||||
| 
 | 
 | ||||||
|  |         voiceAnnouncements = new VoiceAnnouncements(this, this); | ||||||
|  | 
 | ||||||
|         infoViews[0]= new InfoViewHolder(findViewById(R.id.recordInfo1Title), findViewById(R.id.recordInfo1Value)); |         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[1]= new InfoViewHolder(findViewById(R.id.recordInfo2Title), findViewById(R.id.recordInfo2Value)); | ||||||
|         infoViews[2]= new InfoViewHolder(findViewById(R.id.recordInfo3Title), findViewById(R.id.recordInfo3Value)); |         infoViews[2]= new InfoViewHolder(findViewById(R.id.recordInfo3Title), findViewById(R.id.recordInfo3Value)); | ||||||
| @ -129,7 +138,9 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location | |||||||
|         waitingForGPSOverlay.clearAnimation(); |         waitingForGPSOverlay.clearAnimation(); | ||||||
|         waitingForGPSOverlay.animate().alpha(0f).setDuration(1000).setListener(new Animator.AnimatorListener() { |         waitingForGPSOverlay.animate().alpha(0f).setDuration(1000).setListener(new Animator.AnimatorListener() { | ||||||
|             @Override public void onAnimationStart(Animator animator) { } |             @Override public void onAnimationStart(Animator animator) { } | ||||||
|  | 
 | ||||||
|             @Override public void onAnimationCancel(Animator animator) { } |             @Override public void onAnimationCancel(Animator animator) { } | ||||||
|  | 
 | ||||||
|             @Override public void onAnimationRepeat(Animator animator) { } |             @Override public void onAnimationRepeat(Animator animator) { } | ||||||
| 
 | 
 | ||||||
|             @Override |             @Override | ||||||
| @ -137,17 +148,16 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location | |||||||
|                 waitingForGPSOverlay.setVisibility(View.GONE); |                 waitingForGPSOverlay.setVisibility(View.GONE); | ||||||
|             } |             } | ||||||
|         }).start(); |         }).start(); | ||||||
|  |         hideOSMAttribution(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void hideOSMAttribution() { | ||||||
|  |         attribution.animate().alpha(0f).setDuration(1000).setStartDelay(5000).start(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void setupMap(){ |     private void setupMap(){ | ||||||
|         this.mapView= new MapView(this); |         this.mapView= new MapView(this); | ||||||
|         TileSources.Purpose purpose; |         downloadLayer = MapManager.setupMap(mapView); | ||||||
|         if(ACTIVITY.equals(Workout.WORKOUT_TYPE_CYCLING)){ |  | ||||||
|             purpose= TileSources.Purpose.CYCLING; |  | ||||||
|         }else{ |  | ||||||
|             purpose= TileSources.Purpose.OUTDOOR; |  | ||||||
|         } |  | ||||||
|         downloadLayer= MapManager.setupMap(mapView, purpose); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void updateLine(){ |     private void updateLine(){ | ||||||
| @ -168,27 +178,33 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location | |||||||
|             try{ |             try{ | ||||||
|                 while (recorder.isActive()){ |                 while (recorder.isActive()){ | ||||||
|                     Thread.sleep(1000); |                     Thread.sleep(1000); | ||||||
|                     if(isResumed){ |  | ||||||
|                     mHandler.post(this::updateDescription); |                     mHandler.post(this::updateDescription); | ||||||
|                 } |                 } | ||||||
|                 } |  | ||||||
|             }catch (InterruptedException e){ |             }catch (InterruptedException e){ | ||||||
|                 e.printStackTrace(); |                 e.printStackTrace(); | ||||||
|             } |             } | ||||||
|         }).start(); |         }).start(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     int i= 0; |  | ||||||
| 
 | 
 | ||||||
|     private void updateDescription(){ |     private void updateDescription() { | ||||||
|         i++; |         long duration = recorder.getDuration(); | ||||||
|         timeView.setText(UnitUtils.getHourMinuteSecondTime(recorder.getDuration())); |         int distanceInMeters = recorder.getDistanceInMeters(); | ||||||
|         infoViews[0].setText(getString(R.string.workoutDistance), UnitUtils.getDistance(recorder.getDistance())); |         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[1].setText(getString(R.string.workoutBurnedEnergy), recorder.getCalories() + " kcal"); | ||||||
|         infoViews[2].setText(getString(R.string.workoutAvgSpeed), UnitUtils.getSpeed(Math.min(100d, recorder.getAvgSpeed()))); |             infoViews[2].setText(getString(R.string.workoutAvgSpeedShort), avgSpeed); | ||||||
|             infoViews[3].setText(getString(R.string.workoutPauseDuration), UnitUtils.getHourMinuteSecondTime(recorder.getPauseDuration())); |             infoViews[3].setText(getString(R.string.workoutPauseDuration), UnitUtils.getHourMinuteSecondTime(recorder.getPauseDuration())); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         voiceAnnouncements.check(recorder); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private void stop(){ |     private void stop(){ | ||||||
|         recorder.stop(); |         recorder.stop(); | ||||||
|         if(recorder.getSampleCount() > 3){ |         if(recorder.getSampleCount() > 3){ | ||||||
| @ -235,14 +251,14 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location | |||||||
|                 .create().show(); |                 .create().show(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void checkPermissions(){ |     private void checkPermissions() { | ||||||
|         if (!hasPermission()) { |         if (!hasPermission()) { | ||||||
|             ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 10); |             ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 10); | ||||||
|             ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_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 |         return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED | ||||||
|                 || ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED; |                 || ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED; | ||||||
|     } |     } | ||||||
| @ -253,12 +269,12 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void stopListener(){ |     private void stopListener() { | ||||||
|         stopService(locationListener); |         stopService(locationListener); | ||||||
|         stopService(pressureService); |         stopService(pressureService); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void startListener(){ |     private void startListener() { | ||||||
|         if(locationListener == null){ |         if(locationListener == null){ | ||||||
|             locationListener= new Intent(this, LocationListener.class); |             locationListener= new Intent(this, LocationListener.class); | ||||||
|             pressureService= new Intent(this, PressureService.class); |             pressureService= new Intent(this, PressureService.class); | ||||||
| @ -304,9 +320,15 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location | |||||||
|     @Override |     @Override | ||||||
|     protected void onDestroy() { |     protected void onDestroy() { | ||||||
|         recorder.stop(); |         recorder.stop(); | ||||||
|         saveIfNotSaved(); // Important |         saveIfNotSaved(); // Important to save | ||||||
|  | 
 | ||||||
|  |         // Clear map | ||||||
|         mapView.destroyAll(); |         mapView.destroyAll(); | ||||||
|         AndroidGraphicFactory.clearResourceMemoryCache(); |         AndroidGraphicFactory.clearResourceMemoryCache(); | ||||||
|  | 
 | ||||||
|  |         // Shutdown TTS | ||||||
|  |         voiceAnnouncements.destroy(); | ||||||
|  | 
 | ||||||
|         super.onDestroy(); |         super.onDestroy(); | ||||||
|         if(wakeLock.isHeld()){ |         if(wakeLock.isHeld()){ | ||||||
|             wakeLock.release(); |             wakeLock.release(); | ||||||
| @ -324,10 +346,18 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location | |||||||
| 
 | 
 | ||||||
|     public void onResume(){ |     public void onResume(){ | ||||||
|         super.onResume(); |         super.onResume(); | ||||||
|  |         enableLockScreenVisibility(); | ||||||
|         downloadLayer.onResume(); |         downloadLayer.onResume(); | ||||||
|         isResumed= true; |         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 |     @Override | ||||||
|     public boolean onCreateOptionsMenu(Menu menu) { |     public boolean onCreateOptionsMenu(Menu menu) { | ||||||
|         // Inflate the menu; this adds items to the action bar if it is present. |         // Inflate the menu; this adds items to the action bar if it is present. | ||||||
| @ -362,13 +392,27 @@ public class RecordWorkoutActivity extends FitoTrackActivity implements Location | |||||||
|                 gpsFound= true; |                 gpsFound= true; | ||||||
|                 hideWaitOverlay(); |                 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{ |     @Override | ||||||
|         TextView titleView, valueView; |     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.titleView = titleView; | ||||||
|             this.valueView = valueView; |             this.valueView = valueView; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -25,127 +25,31 @@ import android.app.AlertDialog; | |||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.content.SharedPreferences; | import android.content.SharedPreferences; | ||||||
| import android.content.pm.PackageManager; | import android.content.pm.PackageManager; | ||||||
| import android.media.Ringtone; |  | ||||||
| import android.media.RingtoneManager; |  | ||||||
| import android.net.Uri; | import android.net.Uri; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.os.Handler; | import android.os.Handler; | ||||||
| import android.preference.ListPreference; |  | ||||||
| import android.preference.Preference; |  | ||||||
| import android.preference.PreferenceActivity; |  | ||||||
| import android.preference.PreferenceManager; | 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.view.View; | ||||||
| import android.widget.NumberPicker; | import android.widget.NumberPicker; | ||||||
|  | import android.widget.Toast; | ||||||
| 
 | 
 | ||||||
| import androidx.annotation.StringRes; |  | ||||||
| import androidx.core.app.ActivityCompat; | import androidx.core.app.ActivityCompat; | ||||||
| import androidx.core.content.FileProvider; | import androidx.core.content.FileProvider; | ||||||
| 
 | 
 | ||||||
| import java.io.BufferedInputStream; |  | ||||||
| import java.io.File; | import java.io.File; | ||||||
| import java.io.FileNotFoundException; | import java.io.IOException; | ||||||
| 
 | 
 | ||||||
| import de.tadris.fitness.R; | import de.tadris.fitness.R; | ||||||
| import de.tadris.fitness.util.export.Exporter; | 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.util.unit.UnitUtils; | ||||||
| import de.tadris.fitness.view.ProgressDialogController; | import de.tadris.fitness.view.ProgressDialogController; | ||||||
| 
 | 
 | ||||||
| public class SettingsActivity extends PreferenceActivity { | public class SettingsActivity extends FitoTrackSettingsActivity { | ||||||
| 
 | 
 | ||||||
| 
 |     private final Handler mHandler = new Handler(); | ||||||
|     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(); |  | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected void onCreate(Bundle savedInstanceState) { |     protected void onCreate(Bundle savedInstanceState) { | ||||||
| @ -158,22 +62,62 @@ public class SettingsActivity extends PreferenceActivity { | |||||||
| 
 | 
 | ||||||
|         bindPreferenceSummaryToValue(findPreference("unitSystem")); |         bindPreferenceSummaryToValue(findPreference("unitSystem")); | ||||||
|         bindPreferenceSummaryToValue(findPreference("mapStyle")); |         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("weight").setOnPreferenceClickListener(preference -> { | ||||||
|         findPreference("import").setOnPreferenceClickListener(preference -> showImportDialog()); |             showWeightPicker(); | ||||||
|         findPreference("export").setOnPreferenceClickListener(preference -> showExportDialog()); |             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) |         new AlertDialog.Builder(this) | ||||||
|                 .setTitle(R.string.exportData) |                 .setTitle(R.string.exportData) | ||||||
|                 .setMessage(R.string.exportDataSummary) |                 .setMessage(R.string.exportDataSummary) | ||||||
|                 .setNegativeButton(R.string.cancel, null) |                 .setNegativeButton(R.string.cancel, null) | ||||||
|                 .setPositiveButton(R.string.backup, (dialog, which) -> { |                 .setPositiveButton(R.string.backup, (dialog, which) -> exportBackup()).create().show(); | ||||||
|                     exportBackup(); |  | ||||||
|                 }).create().show(); |  | ||||||
|         return true; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void exportBackup(){ |     private void exportBackup(){ | ||||||
| @ -182,15 +126,18 @@ public class SettingsActivity extends PreferenceActivity { | |||||||
|         new Thread(() -> { |         new Thread(() -> { | ||||||
|             try{ |             try{ | ||||||
|                 String file= getFilesDir().getAbsolutePath() + "/shared/backup.ftb"; |                 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)); |                 Uri uri= FileProvider.getUriForFile(getBaseContext(), "de.tadris.fitness.fileprovider", new File(file)); | ||||||
| 
 | 
 | ||||||
|                 Exporter.exportData(getBaseContext(), new File(file), |                 BackupController backupController= new BackupController(getBaseContext(), new File(file), (progress, action) -> mHandler.post(() -> dialogController.setProgress(progress, action))); | ||||||
|                         (progress, action) -> mHandler.post(() -> dialogController.setProgress(progress, action))); |                 backupController.exportData(); | ||||||
| 
 | 
 | ||||||
|                 mHandler.post(() -> { |                 mHandler.post(() -> { | ||||||
|                     dialogController.cancel(); |                     dialogController.cancel(); | ||||||
|                     shareFile(uri); |                     FileUtils.saveOrShareFile(this, uri, "ftb"); | ||||||
|                 }); |                 }); | ||||||
|             }catch (Exception e){ |             }catch (Exception e){ | ||||||
|                 e.printStackTrace(); |                 e.printStackTrace(); | ||||||
| @ -202,28 +149,25 @@ public class SettingsActivity extends PreferenceActivity { | |||||||
|         }).start(); |         }).start(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private boolean showImportDialog(){ |     private void showImportDialog() { | ||||||
|         if(!hasPermission()){ |         if(!hasPermission()){ | ||||||
|             requestPermissions(); |             requestPermissions(); | ||||||
|             return true; |             return; | ||||||
|         } |         } | ||||||
|         new AlertDialog.Builder(this) |         new AlertDialog.Builder(this) | ||||||
|                 .setTitle(R.string.importBackup) |                 .setTitle(R.string.importBackup) | ||||||
|                 .setMessage(R.string.importBackupMessage) |                 .setMessage(R.string.importBackupMessage) | ||||||
|                 .setNegativeButton(R.string.cancel, null) |                 .setNegativeButton(R.string.cancel, null) | ||||||
|                 .setPositiveButton(R.string.restore, (dialog, which) -> { |                 .setPositiveButton(R.string.restore, (dialog, which) -> importBackup()).create().show(); | ||||||
|                     importBackup(); |  | ||||||
|                 }).create().show(); |  | ||||||
|         return true; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void requestPermissions(){ |     private void requestPermissions() { | ||||||
|         if (!hasPermission()) { |         if (!hasPermission()) { | ||||||
|             ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 10); |             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; |         return ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -239,12 +183,10 @@ public class SettingsActivity extends PreferenceActivity { | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected void onActivityResult(int requestCode, int resultCode, Intent data) { |     protected void onActivityResult(int requestCode, int resultCode, Intent data) { | ||||||
|         switch (requestCode) { |         if (requestCode == FILE_SELECT_CODE) { | ||||||
|             case FILE_SELECT_CODE: |             if (resultCode == RESULT_OK) { | ||||||
|                 if (resultCode == RESULT_OK){ |  | ||||||
|                 importBackup(data.getData()); |                 importBackup(data.getData()); | ||||||
|             } |             } | ||||||
|                 break; |  | ||||||
|         } |         } | ||||||
|         super.onActivityResult(requestCode, resultCode, data); |         super.onActivityResult(requestCode, resultCode, data); | ||||||
|     } |     } | ||||||
| @ -254,10 +196,10 @@ public class SettingsActivity extends PreferenceActivity { | |||||||
|         dialogController.show(); |         dialogController.show(); | ||||||
|         new Thread(() -> { |         new Thread(() -> { | ||||||
|             try{ |             try{ | ||||||
|                 Exporter.importData(getBaseContext(), uri, |                 RestoreController restoreController= new RestoreController(getBaseContext(), uri, | ||||||
|                         (progress, action) -> mHandler.post(() -> dialogController.setProgress(progress, action))); |                         (progress, action) -> mHandler.post(() -> dialogController.setProgress(progress, action))); | ||||||
|  |                 restoreController.restoreData(); | ||||||
| 
 | 
 | ||||||
|                 // DO on backup finished |  | ||||||
|                 mHandler.post(dialogController::cancel); |                 mHandler.post(dialogController::cancel); | ||||||
|             }catch (Exception e){ |             }catch (Exception e){ | ||||||
|                 e.printStackTrace(); |                 e.printStackTrace(); | ||||||
| @ -269,7 +211,7 @@ public class SettingsActivity extends PreferenceActivity { | |||||||
|         }).start(); |         }).start(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private boolean showWeightPicker() { |     private void showWeightPicker() { | ||||||
|         UnitUtils.setUnit(this); // Maybe the user changed unit system |         UnitUtils.setUnit(this); // Maybe the user changed unit system | ||||||
| 
 | 
 | ||||||
|         final AlertDialog.Builder d = new AlertDialog.Builder(this); |         final AlertDialog.Builder d = new AlertDialog.Builder(this); | ||||||
| @ -280,7 +222,8 @@ public class SettingsActivity extends PreferenceActivity { | |||||||
|         np.setMaxValue((int) UnitUtils.CHOSEN_SYSTEM.getWeightFromKilogram(150)); |         np.setMaxValue((int) UnitUtils.CHOSEN_SYSTEM.getWeightFromKilogram(150)); | ||||||
|         np.setMinValue((int) UnitUtils.CHOSEN_SYSTEM.getWeightFromKilogram(20)); |         np.setMinValue((int) UnitUtils.CHOSEN_SYSTEM.getWeightFromKilogram(20)); | ||||||
|         np.setFormatter(value -> value + " " + UnitUtils.CHOSEN_SYSTEM.getWeightUnit()); |         np.setFormatter(value -> value + " " + UnitUtils.CHOSEN_SYSTEM.getWeightUnit()); | ||||||
|         np.setValue((int)Math.round(UnitUtils.CHOSEN_SYSTEM.getWeightFromKilogram(preferences.getInt("weight", 80)))); |         final String preferenceVariable = "weight"; | ||||||
|  |         np.setValue((int)Math.round(UnitUtils.CHOSEN_SYSTEM.getWeightFromKilogram(preferences.getInt(preferenceVariable, 80)))); | ||||||
|         np.setWrapSelectorWheel(false); |         np.setWrapSelectorWheel(false); | ||||||
| 
 | 
 | ||||||
|         d.setView(v); |         d.setView(v); | ||||||
| @ -289,12 +232,10 @@ public class SettingsActivity extends PreferenceActivity { | |||||||
|         d.setPositiveButton(R.string.okay, (dialog, which) -> { |         d.setPositiveButton(R.string.okay, (dialog, which) -> { | ||||||
|             int unitValue= np.getValue(); |             int unitValue= np.getValue(); | ||||||
|             int kilograms= (int)Math.round(UnitUtils.CHOSEN_SYSTEM.getKilogramFromUnit(unitValue)); |             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(); |         d.create().show(); | ||||||
| 
 |  | ||||||
|         return true; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -308,24 +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) { |  | ||||||
|             finish(); |  | ||||||
|             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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -20,25 +20,20 @@ | |||||||
| package de.tadris.fitness.activity; | package de.tadris.fitness.activity; | ||||||
| 
 | 
 | ||||||
| import android.app.AlertDialog; | import android.app.AlertDialog; | ||||||
| import android.app.Dialog; |  | ||||||
| import android.content.DialogInterface; |  | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.graphics.Typeface; |  | ||||||
| import android.net.Uri; | import android.net.Uri; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.util.TypedValue; |  | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| import android.view.MenuItem; | import android.view.MenuItem; | ||||||
| import android.view.View; |  | ||||||
| import android.widget.CheckBox; | import android.widget.CheckBox; | ||||||
| import android.widget.EditText; | import android.widget.EditText; | ||||||
| import android.widget.Spinner; | import android.widget.Spinner; | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
| import android.widget.Toast; |  | ||||||
| 
 | 
 | ||||||
| import androidx.core.content.FileProvider; | import androidx.core.content.FileProvider; | ||||||
| 
 | 
 | ||||||
| import java.io.File; | import java.io.File; | ||||||
|  | import java.io.IOException; | ||||||
| import java.text.SimpleDateFormat; | import java.text.SimpleDateFormat; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Date; | import java.util.Date; | ||||||
| @ -50,6 +45,7 @@ import de.tadris.fitness.data.WorkoutSample; | |||||||
| import de.tadris.fitness.osm.OAuthAuthentication; | import de.tadris.fitness.osm.OAuthAuthentication; | ||||||
| import de.tadris.fitness.osm.OsmTraceUploader; | import de.tadris.fitness.osm.OsmTraceUploader; | ||||||
| import de.tadris.fitness.util.DialogUtils; | import de.tadris.fitness.util.DialogUtils; | ||||||
|  | import de.tadris.fitness.util.FileUtils; | ||||||
| import de.tadris.fitness.util.gpx.GpxExporter; | import de.tadris.fitness.util.gpx.GpxExporter; | ||||||
| import de.tadris.fitness.util.unit.UnitUtils; | import de.tadris.fitness.util.unit.UnitUtils; | ||||||
| import de.tadris.fitness.view.ProgressDialogController; | import de.tadris.fitness.view.ProgressDialogController; | ||||||
| @ -58,6 +54,7 @@ import oauth.signpost.OAuthConsumer; | |||||||
| 
 | 
 | ||||||
| public class ShowWorkoutActivity extends WorkoutActivity implements DialogUtils.WorkoutDeleter { | public class ShowWorkoutActivity extends WorkoutActivity implements DialogUtils.WorkoutDeleter { | ||||||
| 
 | 
 | ||||||
|  |     TextView commentView; | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected void onCreate(Bundle savedInstanceState) { |     protected void onCreate(Bundle savedInstanceState) { | ||||||
| @ -66,14 +63,13 @@ public class ShowWorkoutActivity extends WorkoutActivity implements DialogUtils. | |||||||
|         initBeforeContent(); |         initBeforeContent(); | ||||||
| 
 | 
 | ||||||
|         setContentView(R.layout.activity_show_workout); |         setContentView(R.layout.activity_show_workout); | ||||||
|         root= findViewById(R.id.showWorkoutRoot); |         initRoot(); | ||||||
| 
 | 
 | ||||||
|         initAfterContent(); |         initAfterContent(); | ||||||
| 
 | 
 | ||||||
|         addText(getString(R.string.comment) + ": " + workout.comment).setOnClickListener(v -> { |         commentView = addText("", true); | ||||||
|             TextView textView= (TextView)v; |         commentView.setOnClickListener(v -> openEditCommentDialog()); | ||||||
|             openEditCommentDialog(textView); |         updateCommentText(); | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         addTitle(getString(R.string.workoutTime)); |         addTitle(getString(R.string.workoutTime)); | ||||||
|         addKeyValue(getString(R.string.workoutDate), getDate()); |         addKeyValue(getString(R.string.workoutDate), getDate()); | ||||||
| @ -84,6 +80,7 @@ public class ShowWorkoutActivity extends WorkoutActivity implements DialogUtils. | |||||||
| 
 | 
 | ||||||
|         addKeyValue(getString(R.string.workoutDistance), UnitUtils.getDistance(workout.length), getString(R.string.workoutPace), UnitUtils.getPace(workout.avgPace)); |         addKeyValue(getString(R.string.workoutDistance), UnitUtils.getDistance(workout.length), getString(R.string.workoutPace), UnitUtils.getPace(workout.avgPace)); | ||||||
| 
 | 
 | ||||||
|  |         if (hasSamples()) { | ||||||
|             addTitle(getString(R.string.workoutRoute)); |             addTitle(getString(R.string.workoutRoute)); | ||||||
| 
 | 
 | ||||||
|             addMap(); |             addMap(); | ||||||
| @ -91,103 +88,83 @@ public class ShowWorkoutActivity extends WorkoutActivity implements DialogUtils. | |||||||
|             map.setClickable(false); |             map.setClickable(false); | ||||||
|             mapRoot.setOnClickListener(v -> startActivity(new Intent(ShowWorkoutActivity.this, ShowWorkoutMapActivity.class))); |             mapRoot.setOnClickListener(v -> startActivity(new Intent(ShowWorkoutActivity.this, ShowWorkoutMapActivity.class))); | ||||||
| 
 | 
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|         addTitle(getString(R.string.workoutSpeed)); |         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)); |                 getString(R.string.workoutTopSpeed), UnitUtils.getSpeed(workout.topSpeed)); | ||||||
| 
 | 
 | ||||||
|  |         if (hasSamples()) { | ||||||
|  | 
 | ||||||
|             addSpeedDiagram(); |             addSpeedDiagram(); | ||||||
| 
 | 
 | ||||||
|             speedDiagram.setOnClickListener(v -> startDiagramActivity(ShowWorkoutMapDiagramActivity.DIAGRAM_TYPE_SPEED)); |             speedDiagram.setOnClickListener(v -> startDiagramActivity(ShowWorkoutMapDiagramActivity.DIAGRAM_TYPE_SPEED)); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         addTitle(getString(R.string.workoutBurnedEnergy)); |         addTitle(getString(R.string.workoutBurnedEnergy)); | ||||||
|         addKeyValue(getString(R.string.workoutTotalEnergy), workout.calorie + " kcal", |         addKeyValue(getString(R.string.workoutTotalEnergy), workout.calorie + " kcal", | ||||||
|                 getString(R.string.workoutEnergyConsumption), UnitUtils.getRelativeEnergyConsumption((double)workout.calorie / ((double)workout.length / 1000))); |                 getString(R.string.workoutEnergyConsumption), UnitUtils.getRelativeEnergyConsumption((double)workout.calorie / ((double)workout.length / 1000))); | ||||||
| 
 | 
 | ||||||
|  |         if (hasSamples()) { | ||||||
|             addTitle(getString(R.string.height)); |             addTitle(getString(R.string.height)); | ||||||
| 
 | 
 | ||||||
|         addKeyValue(getString(R.string.workoutAscent), UnitUtils.getDistance((int)workout.ascent), |             addKeyValue(getString(R.string.workoutAscent), UnitUtils.getDistance((int) workout.ascent), | ||||||
|                 getString(R.string.workoutDescent), UnitUtils.getDistance((int)workout.descent)); |                     getString(R.string.workoutDescent), UnitUtils.getDistance((int) workout.descent)); | ||||||
| 
 | 
 | ||||||
|             addHeightDiagram(); |             addHeightDiagram(); | ||||||
| 
 | 
 | ||||||
|             heightDiagram.setOnClickListener(v -> startDiagramActivity(ShowWorkoutMapDiagramActivity.DIAGRAM_TYPE_HEIGHT)); |             heightDiagram.setOnClickListener(v -> startDiagramActivity(ShowWorkoutMapDiagramActivity.DIAGRAM_TYPE_HEIGHT)); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void startDiagramActivity(String diagramType){ |     private void startDiagramActivity(String diagramType) { | ||||||
|         ShowWorkoutMapDiagramActivity.DIAGRAM_TYPE= diagramType; |         ShowWorkoutMapDiagramActivity.DIAGRAM_TYPE= diagramType; | ||||||
|         startActivity(new Intent(ShowWorkoutActivity.this, ShowWorkoutMapDiagramActivity.class)); |         startActivity(new Intent(ShowWorkoutActivity.this, ShowWorkoutMapDiagramActivity.class)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     void openEditCommentDialog(final TextView change){ |     private void openEditCommentDialog() { | ||||||
|         final EditText editText= new EditText(this); |         final EditText editText= new EditText(this); | ||||||
|         editText.setText(workout.comment); |         editText.setText(workout.comment); | ||||||
|         editText.setSingleLine(true); |         editText.setSingleLine(true); | ||||||
|         new AlertDialog.Builder(this) |         new AlertDialog.Builder(this) | ||||||
|                 .setTitle(R.string.enterComment) |                 .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(); |                 .setView(editText).create().show(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void changeComment(String comment, TextView onChange){ |     private void changeComment(String comment) { | ||||||
|         workout.comment= comment; |         workout.comment= comment; | ||||||
|         Instance.getInstance(this).db.workoutDao().updateWorkout(workout); |         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)); |         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); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     @Override |     @Override | ||||||
|     public boolean onCreateOptionsMenu(Menu menu) { |     public boolean onCreateOptionsMenu(Menu menu) { | ||||||
|         // Inflate the menu; this adds items to the action bar if it is present. |         // Inflate the menu; this adds items to the action bar if it is present. | ||||||
| @ -205,19 +182,27 @@ public class ShowWorkoutActivity extends WorkoutActivity implements DialogUtils. | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void exportToGpx(){ |     private void exportToGpx(){ | ||||||
|  |         if (!hasStoragePermission()) { | ||||||
|  |             requestStoragePermissions(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|         ProgressDialogController dialogController= new ProgressDialogController(this, getString(R.string.exporting)); |         ProgressDialogController dialogController= new ProgressDialogController(this, getString(R.string.exporting)); | ||||||
|         dialogController.setIndeterminate(true); |         dialogController.setIndeterminate(true); | ||||||
|         dialogController.show(); |         dialogController.show(); | ||||||
|         new Thread(() -> { |         new Thread(() -> { | ||||||
|             try{ |             try{ | ||||||
|                 String file= getFilesDir().getAbsolutePath() + "/shared/workout.gpx"; |                 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)); |                 Uri uri= FileProvider.getUriForFile(getBaseContext(), "de.tadris.fitness.fileprovider", new File(file)); | ||||||
| 
 | 
 | ||||||
|                 GpxExporter.exportWorkout(getBaseContext(), workout, new File(file)); |                 GpxExporter.exportWorkout(getBaseContext(), workout, new File(file)); | ||||||
|                 dialogController.cancel(); |                 dialogController.cancel(); | ||||||
|                 mHandler.post(() -> shareFile(uri)); |                 mHandler.post(() -> FileUtils.saveOrShareFile(this, uri, "gpx")); | ||||||
|             }catch (Exception e){ |             }catch (Exception e){ | ||||||
|  |                 e.printStackTrace(); | ||||||
|                 mHandler.post(() -> showErrorDialog(e, R.string.error, R.string.errorGpxExportFailed)); |                 mHandler.post(() -> showErrorDialog(e, R.string.error, R.string.errorGpxExportFailed)); | ||||||
|             } |             } | ||||||
|         }).start(); |         }).start(); | ||||||
| @ -245,7 +230,7 @@ public class ShowWorkoutActivity extends WorkoutActivity implements DialogUtils. | |||||||
|         authentication.authenticateIfNecessary(); |         authentication.authenticateIfNecessary(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     AlertDialog dialog= null; |     private AlertDialog dialog = null; | ||||||
|     private void showUploadOptions(){ |     private void showUploadOptions(){ | ||||||
|         dialog= new AlertDialog.Builder(this) |         dialog= new AlertDialog.Builder(this) | ||||||
|                 .setTitle(R.string.actionUploadToOSM) |                 .setTitle(R.string.actionUploadToOSM) | ||||||
| @ -286,8 +271,15 @@ public class ShowWorkoutActivity extends WorkoutActivity implements DialogUtils. | |||||||
|             case R.id.actionUploadOSM: |             case R.id.actionUploadOSM: | ||||||
|                 prepareUpload(); |                 prepareUpload(); | ||||||
|                 return true; |                 return true; | ||||||
|  |             case R.id.actionEditComment: | ||||||
|  |                 openEditCommentDialog(); | ||||||
|  |                 return true; | ||||||
|         } |         } | ||||||
|         return super.onOptionsItemSelected(item); |         return super.onOptionsItemSelected(item); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     void initRoot() { | ||||||
|  |         root = findViewById(R.id.showWorkoutRoot); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -32,7 +32,7 @@ public class ShowWorkoutMapActivity extends WorkoutActivity { | |||||||
|         initBeforeContent(); |         initBeforeContent(); | ||||||
| 
 | 
 | ||||||
|         setContentView(R.layout.activity_show_workout_map); |         setContentView(R.layout.activity_show_workout_map); | ||||||
|         root= findViewById(R.id.showWorkoutMapParent); |         initRoot(); | ||||||
| 
 | 
 | ||||||
|         initAfterContent(); |         initAfterContent(); | ||||||
| 
 | 
 | ||||||
| @ -43,4 +43,8 @@ public class ShowWorkoutMapActivity extends WorkoutActivity { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     void initRoot() { | ||||||
|  |         root = findViewById(R.id.showWorkoutMapParent); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -36,7 +36,7 @@ public class ShowWorkoutMapDiagramActivity extends WorkoutActivity { | |||||||
|         initBeforeContent(); |         initBeforeContent(); | ||||||
| 
 | 
 | ||||||
|         setContentView(R.layout.activity_show_workout_map_diagram); |         setContentView(R.layout.activity_show_workout_map_diagram); | ||||||
|         root= findViewById(R.id.showWorkoutMapParent); |         initRoot(); | ||||||
| 
 | 
 | ||||||
|         initAfterContent(); |         initAfterContent(); | ||||||
| 
 | 
 | ||||||
| @ -54,4 +54,8 @@ public class ShowWorkoutMapDiagramActivity extends WorkoutActivity { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |     @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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -55,46 +55,45 @@ import de.tadris.fitness.data.WorkoutManager; | |||||||
| import de.tadris.fitness.data.WorkoutSample; | import de.tadris.fitness.data.WorkoutSample; | ||||||
| import de.tadris.fitness.map.MapManager; | import de.tadris.fitness.map.MapManager; | ||||||
| import de.tadris.fitness.map.WorkoutLayer; | import de.tadris.fitness.map.WorkoutLayer; | ||||||
| import de.tadris.fitness.map.tilesource.TileSources; |  | ||||||
| import de.tadris.fitness.util.ThemeManager; |  | ||||||
| import de.tadris.fitness.util.WorkoutTypeCalculator; |  | ||||||
| import de.tadris.fitness.util.unit.UnitUtils; | import de.tadris.fitness.util.unit.UnitUtils; | ||||||
| 
 | 
 | ||||||
| public abstract class WorkoutActivity extends FitoTrackActivity { | public abstract class WorkoutActivity extends InformationActivity { | ||||||
| 
 | 
 | ||||||
|     public static Workout selectedWorkout; |     public static Workout selectedWorkout; | ||||||
| 
 | 
 | ||||||
|     protected List<WorkoutSample> samples; |     List<WorkoutSample> samples; | ||||||
|     protected Workout workout; |     Workout workout; | ||||||
|     protected ViewGroup root; |     private Resources.Theme theme; | ||||||
|     protected Resources.Theme theme; |     MapView map; | ||||||
|     protected MapView map; |     private TileDownloadLayer downloadLayer; | ||||||
|     protected TileDownloadLayer downloadLayer; |     private FixedPixelCircle highlightingCircle; | ||||||
|     protected FixedPixelCircle highlightingCircle; |     final Handler mHandler = new Handler(); | ||||||
|     protected Handler mHandler= new Handler(); |  | ||||||
| 
 | 
 | ||||||
|     protected LineChart speedDiagram, heightDiagram; |     LineChart speedDiagram; | ||||||
|  |     LineChart heightDiagram; | ||||||
| 
 | 
 | ||||||
|     protected void initBeforeContent(){ |     void initBeforeContent() { | ||||||
|         workout= selectedWorkout; |         workout= selectedWorkout; | ||||||
|         samples= Arrays.asList(Instance.getInstance(this).db.workoutDao().getAllSamplesOfWorkout(workout.id)); |         samples= Arrays.asList(Instance.getInstance(this).db.workoutDao().getAllSamplesOfWorkout(workout.id)); | ||||||
|         setTheme(ThemeManager.getThemeByWorkout(workout)); |         setTheme(Instance.getInstance(this).themes.getWorkoutTypeTheme(workout.getWorkoutType())); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected void initAfterContent(){ |     void initAfterContent() { | ||||||
|  |         if (getActionBar() != null) { | ||||||
|             getActionBar().setDisplayHomeAsUpEnabled(true); |             getActionBar().setDisplayHomeAsUpEnabled(true); | ||||||
|         setTitle(WorkoutTypeCalculator.getType(workout)); |         } | ||||||
|  |         setTitle(workout.getWorkoutType().title); | ||||||
| 
 | 
 | ||||||
|         theme= getTheme(); |         theme= getTheme(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void addDiagram(SampleConverter converter){ |     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)); |         root.addView(getDiagram(converter), new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, fullScreenItems ? ViewGroup.LayoutParams.MATCH_PARENT : getWindowManager().getDefaultDisplay().getWidth()*3/4)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected boolean diagramsInteractive= false; |     boolean diagramsInteractive = false; | ||||||
| 
 | 
 | ||||||
|     LineChart getDiagram(SampleConverter converter){ |     private LineChart getDiagram(SampleConverter converter) { | ||||||
|         LineChart chart= new LineChart(this); |         LineChart chart= new LineChart(this); | ||||||
| 
 | 
 | ||||||
|         converter.onCreate(); |         converter.onCreate(); | ||||||
| @ -112,7 +111,7 @@ public abstract class WorkoutActivity extends FitoTrackActivity { | |||||||
|         dataSet.setValueTextColor(getThemePrimaryColor()); |         dataSet.setValueTextColor(getThemePrimaryColor()); | ||||||
|         dataSet.setDrawCircles(false); |         dataSet.setDrawCircles(false); | ||||||
|         dataSet.setLineWidth(4); |         dataSet.setLineWidth(4); | ||||||
|         dataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER); |         dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER); | ||||||
| 
 | 
 | ||||||
|         Description description= new Description(); |         Description description= new Description(); | ||||||
|         description.setText(converter.getDescription()); |         description.setText(converter.getDescription()); | ||||||
| @ -145,7 +144,7 @@ public abstract class WorkoutActivity extends FitoTrackActivity { | |||||||
|         return chart; |         return chart; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected void onDiagramValueSelected(LatLong latLong){ |     private void onDiagramValueSelected(LatLong latLong) { | ||||||
|         Paint p= AndroidGraphicFactory.INSTANCE.createPaint(); |         Paint p= AndroidGraphicFactory.INSTANCE.createPaint(); | ||||||
|         p.setColor(0xff693cff); |         p.setColor(0xff693cff); | ||||||
|         highlightingCircle= new FixedPixelCircle(latLong, 20, p, null); |         highlightingCircle= new FixedPixelCircle(latLong, 20, p, null); | ||||||
| @ -166,7 +165,7 @@ public abstract class WorkoutActivity extends FitoTrackActivity { | |||||||
|         void afterAdd(LineChart chart); |         void afterAdd(LineChart chart); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     WorkoutSample findSample(SampleConverter converter, Entry entry){ |     private WorkoutSample findSample(SampleConverter converter, Entry entry) { | ||||||
|         for(WorkoutSample sample : samples){ |         for(WorkoutSample sample : samples){ | ||||||
|             if(converter.compare(sample, entry)){ |             if(converter.compare(sample, entry)){ | ||||||
|                 return sample; |                 return sample; | ||||||
| @ -251,12 +250,12 @@ public abstract class WorkoutActivity extends FitoTrackActivity { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected boolean fullScreenItems = false; |     boolean fullScreenItems = false; | ||||||
|     protected LinearLayout mapRoot; |     LinearLayout mapRoot; | ||||||
| 
 | 
 | ||||||
|     void addMap(){ |     void addMap(){ | ||||||
|         map= new MapView(this); |         map= new MapView(this); | ||||||
|         downloadLayer= MapManager.setupMap(map, TileSources.Purpose.DEFAULT); |         downloadLayer = MapManager.setupMap(map); | ||||||
| 
 | 
 | ||||||
|         WorkoutLayer workoutLayer= new WorkoutLayer(samples, getThemePrimaryColor()); |         WorkoutLayer workoutLayer= new WorkoutLayer(samples, getThemePrimaryColor()); | ||||||
|         map.addLayer(workoutLayer); |         map.addLayer(workoutLayer); | ||||||
| @ -290,13 +289,19 @@ public abstract class WorkoutActivity extends FitoTrackActivity { | |||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     int getMapHeight(){ |     private int getMapHeight() { | ||||||
|         return getWindowManager().getDefaultDisplay().getWidth()*3/4; |         return getWindowManager().getDefaultDisplay().getWidth()*3/4; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     protected boolean hasSamples() { | ||||||
|  |         return samples.size() > 1; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected void onDestroy() { |     protected void onDestroy() { | ||||||
|  |         if (map != null) { | ||||||
|             map.destroyAll(); |             map.destroyAll(); | ||||||
|  |         } | ||||||
|         AndroidGraphicFactory.clearResourceMemoryCache(); |         AndroidGraphicFactory.clearResourceMemoryCache(); | ||||||
|         super.onDestroy(); |         super.onDestroy(); | ||||||
|     } |     } | ||||||
| @ -304,13 +309,17 @@ public abstract class WorkoutActivity extends FitoTrackActivity { | |||||||
|     @Override |     @Override | ||||||
|     public void onPause(){ |     public void onPause(){ | ||||||
|         super.onPause(); |         super.onPause(); | ||||||
|  |         if (downloadLayer != null) { | ||||||
|             downloadLayer.onPause(); |             downloadLayer.onPause(); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     public void onResume(){ |     public void onResume(){ | ||||||
|         super.onResume(); |         super.onResume(); | ||||||
|  |         if (downloadLayer != null) { | ||||||
|             downloadLayer.onResume(); |             downloadLayer.onResume(); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public boolean onOptionsItemSelected(MenuItem item) { |     public boolean onOptionsItemSelected(MenuItem 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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -22,7 +22,7 @@ package de.tadris.fitness.data; | |||||||
| import androidx.room.Database; | import androidx.room.Database; | ||||||
| import androidx.room.RoomDatabase; | import androidx.room.RoomDatabase; | ||||||
| 
 | 
 | ||||||
| @Database(version = 2, entities = {Workout.class,  WorkoutSample.class}) | @Database(version = 3, entities = {Workout.class, WorkoutSample.class}) | ||||||
| public abstract class AppDatabase extends RoomDatabase { | public abstract class AppDatabase extends RoomDatabase { | ||||||
|     public abstract WorkoutDao workoutDao(); |     public abstract WorkoutDao workoutDao(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -25,7 +25,7 @@ import android.preference.PreferenceManager; | |||||||
| 
 | 
 | ||||||
| public class UserPreferences { | public class UserPreferences { | ||||||
| 
 | 
 | ||||||
|     private SharedPreferences preferences; |     private final SharedPreferences preferences; | ||||||
| 
 | 
 | ||||||
|     public UserPreferences(Context context) { |     public UserPreferences(Context context) { | ||||||
|         this.preferences= PreferenceManager.getDefaultSharedPreferences(context); |         this.preferences= PreferenceManager.getDefaultSharedPreferences(context); | ||||||
| @ -35,6 +35,14 @@ public class UserPreferences { | |||||||
|         return preferences.getInt("weight", 80); |         return preferences.getInt("weight", 80); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public int getSpokenUpdateTimePeriod(){ | ||||||
|  |         return preferences.getInt("spokenUpdateTimePeriod", 0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getSpokenUpdateDistancePeriod(){ | ||||||
|  |         return preferences.getInt("spokenUpdateDistancePeriod", 0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public String getMapStyle(){ |     public String getMapStyle(){ | ||||||
|         return preferences.getString("mapStyle", "osm.mapnik"); |         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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -19,10 +19,13 @@ | |||||||
| 
 | 
 | ||||||
| package de.tadris.fitness.data; | package de.tadris.fitness.data; | ||||||
| 
 | 
 | ||||||
|  | import androidx.room.ColumnInfo; | ||||||
| import androidx.room.Entity; | import androidx.room.Entity; | ||||||
| import androidx.room.PrimaryKey; | import androidx.room.PrimaryKey; | ||||||
| 
 | 
 | ||||||
|  | import com.fasterxml.jackson.annotation.JsonIgnore; | ||||||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||||||
|  | import com.fasterxml.jackson.annotation.JsonProperty; | ||||||
| 
 | 
 | ||||||
| import java.text.SimpleDateFormat; | import java.text.SimpleDateFormat; | ||||||
| import java.util.Date; | import java.util.Date; | ||||||
| @ -31,10 +34,6 @@ import java.util.Date; | |||||||
| @JsonIgnoreProperties(ignoreUnknown = true) | @JsonIgnoreProperties(ignoreUnknown = true) | ||||||
| public class Workout{ | 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 |     @PrimaryKey | ||||||
|     public long id; |     public long id; | ||||||
| 
 | 
 | ||||||
| @ -63,12 +62,13 @@ public class Workout{ | |||||||
|     public double topSpeed; |     public double topSpeed; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Average pace of workout in km/min |      * Average pace of workout in min/km | ||||||
|      */ |      */ | ||||||
|     public double avgPace; |     public double avgPace; | ||||||
| 
 | 
 | ||||||
|     public String workoutType; |     @ColumnInfo(name = "workoutType") | ||||||
| 
 |     @JsonProperty(value = "workoutType") | ||||||
|  |     public String workoutTypeId; | ||||||
| 
 | 
 | ||||||
|     public float ascent; |     public float ascent; | ||||||
| 
 | 
 | ||||||
| @ -76,6 +76,8 @@ public class Workout{ | |||||||
| 
 | 
 | ||||||
|     public int calorie; |     public int calorie; | ||||||
| 
 | 
 | ||||||
|  |     public boolean edited; | ||||||
|  | 
 | ||||||
|     public String toString(){ |     public String toString(){ | ||||||
|         if(comment.length() > 2){ |         if(comment.length() > 2){ | ||||||
|             return comment; |             return comment; | ||||||
| @ -84,9 +86,20 @@ public class Workout{ | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @JsonIgnore | ||||||
|     public String getDateString(){ |     public String getDateString(){ | ||||||
|         return SimpleDateFormat.getDateTimeInstance().format(new Date(start)); |         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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -19,118 +19,10 @@ | |||||||
| 
 | 
 | ||||||
| package de.tadris.fitness.data; | package de.tadris.fitness.data; | ||||||
| 
 | 
 | ||||||
| import android.content.Context; |  | ||||||
| import android.hardware.SensorManager; |  | ||||||
| import android.util.Log; |  | ||||||
| 
 |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| import de.tadris.fitness.Instance; |  | ||||||
| import de.tadris.fitness.util.CalorieCalculator; |  | ||||||
| 
 |  | ||||||
| public class WorkoutManager { | 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; |  | ||||||
|         double elevationSum= 0; // Sum of elevation |  | ||||||
|         double pressureSum= 0; // Sum of elevation |  | ||||||
|         for(WorkoutSample sample : samples){ |  | ||||||
|             i++; |  | ||||||
|             sample.id= workout.id + i; |  | ||||||
|             sample.workoutId= workout.id; |  | ||||||
|             elevationSum+= sample.elevation; |  | ||||||
|             pressureSum+= sample.tmpPressure; |  | ||||||
|             if(sample.speed > topSpeed){ |  | ||||||
|                 topSpeed= sample.speed; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         workout.topSpeed= topSpeed; |  | ||||||
| 
 |  | ||||||
|         // Calculating height data |  | ||||||
|         boolean pressureDataAvailable= samples.get(0).tmpPressure != -1; |  | ||||||
|         double avgElevation= elevationSum / samples.size(); |  | ||||||
|         double avgPressure=  pressureSum  / samples.size(); |  | ||||||
| 
 |  | ||||||
|         workout.ascent = 0; |  | ||||||
|         workout.descent = 0; |  | ||||||
| 
 |  | ||||||
|         for(i= 0; i < samples.size(); i++){ |  | ||||||
|             WorkoutSample sample= samples.get(i); |  | ||||||
| 
 |  | ||||||
|             if(pressureDataAvailable){ |  | ||||||
|                 // 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; |  | ||||||
|             } // Else: use already set GPS elevation in WorkoutSample.elevation |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         int range= 3; |  | ||||||
|         for(i= 0; i < samples.size(); i++){ |  | ||||||
|             int min= Math.max(i-range, 0); |  | ||||||
|             int max= Math.min(i+range, samples.size()-1); |  | ||||||
|             samples.get(i).tmpElevation= getAverageElevation(samples.subList(min, max)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         for(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){ |  | ||||||
|                     workout.ascent += diff; |  | ||||||
|                 }else{ |  | ||||||
|                     workout.descent += Math.abs(diff); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Saving workout and samples |  | ||||||
|         db.workoutDao().insertWorkoutAndSamples(workout, samples.toArray(new WorkoutSample[0])); |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static double getAverageElevation(List<WorkoutSample> samples){ |  | ||||||
|         double sum= 0; |  | ||||||
|         for(WorkoutSample sample : samples){ |  | ||||||
|             sum+= sample.elevation; |  | ||||||
|         } |  | ||||||
|         return sum / samples.size(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static void roundSpeedValues(List<WorkoutSample> samples){ |     public static void roundSpeedValues(List<WorkoutSample> samples){ | ||||||
|         for(int i= 0; i < samples.size(); i++){ |         for(int i= 0; i < samples.size(); i++){ | ||||||
|             WorkoutSample sample= samples.get(i); |             WorkoutSample sample= samples.get(i); | ||||||
|  | |||||||
							
								
								
									
										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); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -17,7 +17,7 @@ | |||||||
|  *     along with this program.  If not, see <http://www.gnu.org/licenses/>. |  *     along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package de.tadris.fitness.util.export; | package de.tadris.fitness.export; | ||||||
| 
 | 
 | ||||||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||||||
| import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; | ||||||
| @ -29,20 +29,18 @@ import de.tadris.fitness.data.WorkoutSample; | |||||||
| 
 | 
 | ||||||
| @JacksonXmlRootElement(localName = "fito-track") | @JacksonXmlRootElement(localName = "fito-track") | ||||||
| @JsonIgnoreProperties(ignoreUnknown = true) | @JsonIgnoreProperties(ignoreUnknown = true) | ||||||
| public class FitoTrackDataContainer { | class FitoTrackDataContainer { | ||||||
| 
 | 
 | ||||||
|     int version; |     private int version; | ||||||
|     List<Workout> workouts; |     private List<Workout> workouts; | ||||||
|     List<WorkoutSample> samples; |     private List<WorkoutSample> samples; | ||||||
|     FitoTrackSettings settings; |  | ||||||
| 
 | 
 | ||||||
|     public FitoTrackDataContainer(){} |     public FitoTrackDataContainer(){} | ||||||
| 
 | 
 | ||||||
|     public FitoTrackDataContainer(int version, List<Workout> workouts, List<WorkoutSample> samples, FitoTrackSettings settings) { |     public FitoTrackDataContainer(int version, List<Workout> workouts, List<WorkoutSample> samples) { | ||||||
|         this.version = version; |         this.version = version; | ||||||
|         this.workouts = workouts; |         this.workouts = workouts; | ||||||
|         this.samples = samples; |         this.samples = samples; | ||||||
|         this.settings = settings; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public int getVersion() { |     public int getVersion() { | ||||||
| @ -69,11 +67,5 @@ public class FitoTrackDataContainer { | |||||||
|         this.samples = samples; |         this.samples = samples; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public FitoTrackSettings getSettings() { |  | ||||||
|         return settings; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     public void setSettings(FitoTrackSettings settings) { |  | ||||||
|         this.settings = settings; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| @ -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,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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -19,7 +19,6 @@ | |||||||
| 
 | 
 | ||||||
| package de.tadris.fitness.map; | package de.tadris.fitness.map; | ||||||
| 
 | 
 | ||||||
| import org.mapsforge.core.model.LatLong; |  | ||||||
| import org.mapsforge.map.android.graphics.AndroidGraphicFactory; | import org.mapsforge.map.android.graphics.AndroidGraphicFactory; | ||||||
| import org.mapsforge.map.android.util.AndroidUtil; | import org.mapsforge.map.android.util.AndroidUtil; | ||||||
| import org.mapsforge.map.android.view.MapView; | import org.mapsforge.map.android.view.MapView; | ||||||
| @ -31,18 +30,19 @@ import de.tadris.fitness.map.tilesource.FitoTrackTileSource; | |||||||
| import de.tadris.fitness.map.tilesource.HumanitarianTileSource; | import de.tadris.fitness.map.tilesource.HumanitarianTileSource; | ||||||
| import de.tadris.fitness.map.tilesource.MapnikTileSource; | import de.tadris.fitness.map.tilesource.MapnikTileSource; | ||||||
| import de.tadris.fitness.map.tilesource.ThunderforestTileSource; | import de.tadris.fitness.map.tilesource.ThunderforestTileSource; | ||||||
| import de.tadris.fitness.map.tilesource.TileSources; |  | ||||||
| 
 | 
 | ||||||
| public class MapManager { | public class MapManager { | ||||||
| 
 | 
 | ||||||
|     public static TileDownloadLayer setupMap(MapView mapView, TileSources.Purpose purpose){ |     public static TileDownloadLayer setupMap(MapView mapView) { | ||||||
|         FitoTrackTileSource tileSource; |         FitoTrackTileSource tileSource; | ||||||
| 
 | 
 | ||||||
|         String chosenTileLayer= Instance.getInstance(mapView.getContext()).userPreferences.getMapStyle(); |         String chosenTileLayer= Instance.getInstance(mapView.getContext()).userPreferences.getMapStyle(); | ||||||
|         switch (chosenTileLayer){ |         switch (chosenTileLayer){ | ||||||
|             case "osm.humanitarian":       tileSource= HumanitarianTileSource.INSTANCE; break; |             case "osm.humanitarian":       tileSource= HumanitarianTileSource.INSTANCE; break; | ||||||
|             case "thunderforest.outdoors": tileSource= ThunderforestTileSource.OUTDOORS; break; |             case "thunderforest.outdoors": tileSource= ThunderforestTileSource.OUTDOORS; break; | ||||||
|             case "thunderforest.cycle":    tileSource= ThunderforestTileSource.CYLE_MAP; break; |             case "thunderforest.cycle": | ||||||
|  |                 tileSource = ThunderforestTileSource.CYCLE_MAP; | ||||||
|  |                 break; | ||||||
|             default:             tileSource= MapnikTileSource.INSTANCE; break; // Inclusive "osm.mapnik" |             default:             tileSource= MapnikTileSource.INSTANCE; break; // Inclusive "osm.mapnik" | ||||||
|         } |         } | ||||||
|         tileSource.setUserAgent("mapsforge-android"); |         tileSource.setUserAgent("mapsforge-android"); | ||||||
| @ -62,7 +62,6 @@ public class MapManager { | |||||||
|         mapView.getLayerManager().redrawLayers(); |         mapView.getLayerManager().redrawLayers(); | ||||||
| 
 | 
 | ||||||
|         mapView.setZoomLevel((byte) 18); |         mapView.setZoomLevel((byte) 18); | ||||||
|         mapView.setCenter(new LatLong(52, 13)); |  | ||||||
| 
 | 
 | ||||||
|         return downloadLayer; |         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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -30,7 +30,7 @@ import de.tadris.fitness.data.WorkoutSample; | |||||||
| 
 | 
 | ||||||
| public class WorkoutLayer extends Polyline { | 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 paint= AndroidGraphicFactory.INSTANCE.createPaint(); | ||||||
|         paint.setStyle(Style.STROKE); |         paint.setStyle(Style.STROKE); | ||||||
|         paint.setColor(color); |         paint.setColor(color); | ||||||
| @ -38,13 +38,13 @@ public class WorkoutLayer extends Polyline { | |||||||
|         return paint; |         return paint; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     List<WorkoutSample> samples; |     private final List<WorkoutSample> samples; | ||||||
| 
 | 
 | ||||||
|     public WorkoutLayer(List<WorkoutSample> samples, int color) { |     public WorkoutLayer(List<WorkoutSample> samples, int color) { | ||||||
|         this(getDEFAULT_PAINT_STROKE(color), samples); |         this(getDEFAULT_PAINT_STROKE(color), samples); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public WorkoutLayer(Paint paintStroke, List<WorkoutSample> samples) { |     private WorkoutLayer(Paint paintStroke, List<WorkoutSample> samples) { | ||||||
|         super(paintStroke, AndroidGraphicFactory.INSTANCE); |         super(paintStroke, AndroidGraphicFactory.INSTANCE); | ||||||
|         this.samples = samples; |         this.samples = samples; | ||||||
|         init(); |         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 |  * 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 abstract class FitoTrackTileSource extends AbstractTileSource { | ||||||
| 
 | 
 | ||||||
|     public FitoTrackTileSource(String[] hostNames, int port) { |     FitoTrackTileSource(String[] hostNames, int port) { | ||||||
|         super(hostNames, port); |         super(hostNames, port); | ||||||
|         defaultTimeToLive = 8279000; |         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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -26,7 +26,7 @@ import java.net.URL; | |||||||
| 
 | 
 | ||||||
| public class HumanitarianTileSource extends FitoTrackTileSource { | 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 int PARALLEL_REQUESTS_LIMIT = 8; | ||||||
|     private static final String PROTOCOL = "https"; |     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 int ZOOM_LEVEL_MIN = 0; | ||||||
|     private static final String NAME = "Humanitarian"; |     private static final String NAME = "Humanitarian"; | ||||||
| 
 | 
 | ||||||
|     public HumanitarianTileSource(String[] hostNames, int port) { |     private HumanitarianTileSource(String[] hostNames, int port) { | ||||||
|         super(hostNames, 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 |  * 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); |             "a.tile.openstreetmap.org", "b.tile.openstreetmap.org", "c.tile.openstreetmap.org"}, 443); | ||||||
|     private static final int PARALLEL_REQUESTS_LIMIT = 8; |     private static final int PARALLEL_REQUESTS_LIMIT = 8; | ||||||
|     private static final String PROTOCOL = "https"; |     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 int ZOOM_LEVEL_MIN = 0; | ||||||
|     private static final String NAME = "OSM Mapnik"; |     private static final String NAME = "OSM Mapnik"; | ||||||
| 
 | 
 | ||||||
|     public MapnikTileSource(String[] hostNames, int port) { |     private MapnikTileSource(String[] hostNames, int port) { | ||||||
|         super(hostNames, 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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -26,19 +26,19 @@ import java.net.URL; | |||||||
| 
 | 
 | ||||||
| public class ThunderforestTileSource extends FitoTrackTileSource{ | 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 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 int PARALLEL_REQUESTS_LIMIT = 8; | ||||||
|     private static final String PROTOCOL = "https"; |     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 static final int ZOOM_LEVEL_MIN = 0; | ||||||
| 
 | 
 | ||||||
|     private String mapName; |     private final String mapName; | ||||||
|     private String name; |     private final String name; | ||||||
| 
 | 
 | ||||||
|     public ThunderforestTileSource(String mapName, String name) { |     private ThunderforestTileSource(String mapName, String name) { | ||||||
|         super(new String[]{"tile.thunderforest.com"}, 443); |         super(new String[]{"tile.thunderforest.com"}, 443); | ||||||
|         this.mapName = mapName; |         this.mapName = mapName; | ||||||
|         this.name = name; |         this.name = name; | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -22,12 +22,12 @@ package de.tadris.fitness.osm; | |||||||
| import de.tadris.fitness.data.WorkoutSample; | import de.tadris.fitness.data.WorkoutSample; | ||||||
| import de.westnordost.osmapi.map.data.LatLon; | import de.westnordost.osmapi.map.data.LatLon; | ||||||
| 
 | 
 | ||||||
| public class GpsTraceLatLong implements LatLon { | class GpsTraceLatLong implements LatLon { | ||||||
| 
 | 
 | ||||||
|     private final double latitude; |     private final double latitude; | ||||||
|     private final double longitude; |     private final double longitude; | ||||||
| 
 | 
 | ||||||
|     public GpsTraceLatLong(double latitude, double longitude) { |     private GpsTraceLatLong(double latitude, double longitude) { | ||||||
|         this.latitude = latitude; |         this.latitude = latitude; | ||||||
|         this.longitude = longitude; |         this.longitude = longitude; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -39,14 +39,14 @@ import oauth.signpost.exception.OAuthException; | |||||||
| 
 | 
 | ||||||
| public class OAuthAuthentication { | public class OAuthAuthentication { | ||||||
| 
 | 
 | ||||||
|     private OAuthConsumer oAuthConsumer= OAuthUrlProvider.getDefaultConsumer(); |     private final OAuthConsumer oAuthConsumer = OAuthUrlProvider.getDefaultConsumer(); | ||||||
|     private OAuthProvider oAuthProvider= OAuthUrlProvider.getDefaultProvider(); |     private final OAuthProvider oAuthProvider = OAuthUrlProvider.getDefaultProvider(); | ||||||
| 
 | 
 | ||||||
|     private Handler handler; |     private final Handler handler; | ||||||
|     private Activity activity; |     private final Activity activity; | ||||||
|     private ProgressDialogController dialogController; |     private final ProgressDialogController dialogController; | ||||||
|     private SharedPreferences preferences; |     private final SharedPreferences preferences; | ||||||
|     private OAuthAuthenticationListener listener; |     private final OAuthAuthenticationListener listener; | ||||||
| 
 | 
 | ||||||
|     public OAuthAuthentication(Handler handler, Activity activity, OAuthAuthenticationListener listener) { |     public OAuthAuthentication(Handler handler, Activity activity, OAuthAuthenticationListener listener) { | ||||||
|         this.handler = handler; |         this.handler = handler; | ||||||
| @ -118,12 +118,12 @@ public class OAuthAuthentication { | |||||||
|                 .putBoolean("authenticated", true).apply(); |                 .putBoolean("authenticated", true).apply(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void clearAccessToken(){ |     void clearAccessToken() { | ||||||
|         preferences.edit().putBoolean("authenticated", false).apply(); |         preferences.edit().putBoolean("authenticated", false).apply(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void retrieveAccessToken(String verificationCode){ |     private void retrieveAccessToken(String verificationCode){ | ||||||
|         handler.post(() -> dialogController.show()); |         handler.post(dialogController::show); | ||||||
|         try{ |         try{ | ||||||
|             oAuthProvider.retrieveAccessToken(oAuthConsumer, verificationCode); |             oAuthProvider.retrieveAccessToken(oAuthConsumer, verificationCode); | ||||||
|             handler.post(() -> { |             handler.post(() -> { | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -24,7 +24,7 @@ import oauth.signpost.OAuthProvider; | |||||||
| import oauth.signpost.basic.DefaultOAuthConsumer; | import oauth.signpost.basic.DefaultOAuthConsumer; | ||||||
| import oauth.signpost.basic.DefaultOAuthProvider; | import oauth.signpost.basic.DefaultOAuthProvider; | ||||||
| 
 | 
 | ||||||
| public class OAuthUrlProvider { | class OAuthUrlProvider { | ||||||
| 
 | 
 | ||||||
|     static private final String CONSUMER_KEY= "jFL9grFmAo5ZS720YDDRXdSOb7F0IZQf9lnY1PHq"; |     static private final String CONSUMER_KEY= "jFL9grFmAo5ZS720YDDRXdSOb7F0IZQf9lnY1PHq"; | ||||||
|     static private final String CONSUMER_SECRET= "oH969vYW60fZLco6E09UQl3uFXqjl4siQbOL0q9q"; |     static private final String CONSUMER_SECRET= "oH969vYW60fZLco6E09UQl3uFXqjl4siQbOL0q9q"; | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -24,6 +24,8 @@ import android.os.Handler; | |||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
| 
 | 
 | ||||||
|  | import androidx.annotation.StringRes; | ||||||
|  | 
 | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.Date; | import java.util.Date; | ||||||
| @ -34,6 +36,7 @@ import de.tadris.fitness.data.Workout; | |||||||
| import de.tadris.fitness.data.WorkoutSample; | import de.tadris.fitness.data.WorkoutSample; | ||||||
| import de.tadris.fitness.view.ProgressDialogController; | import de.tadris.fitness.view.ProgressDialogController; | ||||||
| import de.westnordost.osmapi.OsmConnection; | import de.westnordost.osmapi.OsmConnection; | ||||||
|  | import de.westnordost.osmapi.common.errors.OsmAuthorizationException; | ||||||
| import de.westnordost.osmapi.traces.GpsTraceDetails; | import de.westnordost.osmapi.traces.GpsTraceDetails; | ||||||
| import de.westnordost.osmapi.traces.GpsTracesDao; | import de.westnordost.osmapi.traces.GpsTracesDao; | ||||||
| import de.westnordost.osmapi.traces.GpsTrackpoint; | import de.westnordost.osmapi.traces.GpsTrackpoint; | ||||||
| @ -43,15 +46,15 @@ public class OsmTraceUploader { | |||||||
| 
 | 
 | ||||||
|     private static final int CUT_DISTANCE= 300; |     private static final int CUT_DISTANCE= 300; | ||||||
| 
 | 
 | ||||||
|     private Activity activity; |     private final Activity activity; | ||||||
|     private Handler handler; |     private final Handler handler; | ||||||
|     private Workout workout; |     private final Workout workout; | ||||||
|     private List<WorkoutSample> samples; |     private final List<WorkoutSample> samples; | ||||||
|     private GpsTraceDetails.Visibility visibility; |     private final GpsTraceDetails.Visibility visibility; | ||||||
|     private OAuthConsumer consumer; |     private final OAuthConsumer consumer; | ||||||
|     private boolean cut; |     private final boolean cut; | ||||||
|     private ProgressDialogController dialogController; |     private final ProgressDialogController dialogController; | ||||||
|     private String description; |     private final String description; | ||||||
| 
 | 
 | ||||||
|     public OsmTraceUploader(Activity activity, Handler handler, Workout workout, List<WorkoutSample> samples, GpsTraceDetails.Visibility visibility, OAuthConsumer consumer, boolean cut, 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.activity = activity; | ||||||
| @ -85,12 +88,19 @@ public class OsmTraceUploader { | |||||||
| 
 | 
 | ||||||
|     public void upload(){ |     public void upload(){ | ||||||
|         new Thread(() -> { |         new Thread(() -> { | ||||||
|             try{ |             try { | ||||||
|                 executeTask(); |                 executeTask(); | ||||||
|             }catch (Exception e){ |             }catch (Exception e){ | ||||||
|                 e.printStackTrace(); |                 e.printStackTrace(); | ||||||
|                 handler.post(() -> { |                 handler.post(() -> { | ||||||
|                     Toast.makeText(activity, R.string.uploadFailed, Toast.LENGTH_LONG).show(); |                     @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(); |                     dialogController.cancel(); | ||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
| @ -98,7 +108,7 @@ public class OsmTraceUploader { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void executeTask(){ |     private void executeTask(){ | ||||||
|         handler.post(() -> dialogController.show()); |         handler.post(dialogController::show); | ||||||
|         setProgress(0); |         setProgress(0); | ||||||
|         if(cut){ cut(); } |         if(cut){ cut(); } | ||||||
|         setProgress(20); |         setProgress(20); | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -51,9 +51,9 @@ public class LocationListener extends Service { | |||||||
|     private static final int LOCATION_INTERVAL = 1000; |     private static final int LOCATION_INTERVAL = 1000; | ||||||
| 
 | 
 | ||||||
|     private class LocationChangedListener implements android.location.LocationListener { |     private class LocationChangedListener implements android.location.LocationListener { | ||||||
|         Location mLastLocation; |         final Location mLastLocation; | ||||||
| 
 | 
 | ||||||
|         public LocationChangedListener(String provider) { |         LocationChangedListener(String provider) { | ||||||
|             Log.i(TAG, "LocationListener " + provider); |             Log.i(TAG, "LocationListener " + provider); | ||||||
|             mLastLocation = new Location(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 |     @Override | ||||||
|     public IBinder onBind(Intent arg0) { |     public IBinder onBind(Intent arg0) { | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -31,32 +31,21 @@ import java.util.List; | |||||||
| 
 | 
 | ||||||
| import de.tadris.fitness.Instance; | import de.tadris.fitness.Instance; | ||||||
| import de.tadris.fitness.data.Workout; | import de.tadris.fitness.data.Workout; | ||||||
| import de.tadris.fitness.data.WorkoutManager; |  | ||||||
| import de.tadris.fitness.data.WorkoutSample; | import de.tadris.fitness.data.WorkoutSample; | ||||||
|  | import de.tadris.fitness.data.WorkoutType; | ||||||
| import de.tadris.fitness.util.CalorieCalculator; | import de.tadris.fitness.util.CalorieCalculator; | ||||||
| 
 | 
 | ||||||
| public class WorkoutRecorder implements LocationListener.LocationChangeListener { | 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 static final int PAUSE_TIME= 10000; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Time after which the workout is stopped and saved automatically because there is no activity anymore |      * 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; |     private static final int AUTO_STOP_TIMEOUT= 1000*60*60*20; // 20 minutes | ||||||
| 
 | 
 | ||||||
|     private Context context; |     private final Context context; | ||||||
|     private Workout workout; |     private final Workout workout; | ||||||
|     private RecordingState state; |     private RecordingState state; | ||||||
|     private final List<WorkoutSample> samples= new ArrayList<>(); |     private final List<WorkoutSample> samples= new ArrayList<>(); | ||||||
|     private long time= 0; |     private long time= 0; | ||||||
| @ -65,25 +54,26 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener | |||||||
|     private long lastPause= 0; |     private long lastPause= 0; | ||||||
|     private long lastSampleTime= 0; |     private long lastSampleTime= 0; | ||||||
|     private double distance= 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 double SIGNAL_BAD_THRESHOLD= 20; // In meters | ||||||
|     private static final int SIGNAL_LOST_THRESHOLD= 10000; // In milliseconds |     private static final int SIGNAL_LOST_THRESHOLD= 10000; // In milliseconds | ||||||
|     private Location lastFix= null; |     private Location lastFix= null; | ||||||
|     private WorkoutRecorderListener workoutRecorderListener; |     private final WorkoutRecorderListener workoutRecorderListener; | ||||||
|     private GpsState gpsState= GpsState.SIGNAL_LOST; |     private GpsState gpsState= GpsState.SIGNAL_LOST; | ||||||
| 
 | 
 | ||||||
|     public WorkoutRecorder(Context context, String workoutType, WorkoutRecorderListener workoutRecorderListener) { |     public WorkoutRecorder(Context context, WorkoutType workoutType, WorkoutRecorderListener workoutRecorderListener) { | ||||||
|         this.context= context; |         this.context= context; | ||||||
|         this.state= RecordingState.IDLE; |         this.state= RecordingState.IDLE; | ||||||
|         this.workoutRecorderListener = workoutRecorderListener; |         this.workoutRecorderListener = workoutRecorderListener; | ||||||
| 
 | 
 | ||||||
|         this.workout= new Workout(); |         this.workout= new Workout(); | ||||||
|  |         workout.edited = false; | ||||||
| 
 | 
 | ||||||
|         // Default values |         // Default values | ||||||
|         this.workout.comment= ""; |         this.workout.comment= ""; | ||||||
| 
 | 
 | ||||||
|         this.workout.workoutType= workoutType; |         this.workout.setWorkoutType(workoutType); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void start(){ |     public void start(){ | ||||||
| @ -120,7 +110,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener | |||||||
|                                     workoutRecorderListener.onAutoStop(); |                                     workoutRecorderListener.onAutoStop(); | ||||||
|                                 } |                                 } | ||||||
|                             }else if(timeDiff > PAUSE_TIME){ |                             }else if(timeDiff > PAUSE_TIME){ | ||||||
|                                 if(state == RecordingState.RUNNING){ |                                 if (state == RecordingState.RUNNING && gpsState != GpsState.SIGNAL_LOST) { | ||||||
|                                     pause(); |                                     pause(); | ||||||
|                                 } |                                 } | ||||||
|                             }else{ |                             }else{ | ||||||
| @ -135,7 +125,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener | |||||||
|             } catch (InterruptedException e) { |             } catch (InterruptedException e) { | ||||||
|                 e.printStackTrace(); |                 e.printStackTrace(); | ||||||
|             } |             } | ||||||
|         }).start(); |         }, "WorkoutWatchdog").start(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void checkSignalState(){ |     private void checkSignalState(){ | ||||||
| @ -165,7 +155,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void pause(){ |     private void pause() { | ||||||
|         if(state == RecordingState.RUNNING){ |         if(state == RecordingState.RUNNING){ | ||||||
|             Log.i("Recorder", "Pause"); |             Log.i("Recorder", "Pause"); | ||||||
|             state= RecordingState.PAUSED; |             state= RecordingState.PAUSED; | ||||||
| @ -193,7 +183,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener | |||||||
|         } |         } | ||||||
|         Log.i("Recorder", "Save"); |         Log.i("Recorder", "Save"); | ||||||
|         synchronized (samples){ |         synchronized (samples){ | ||||||
|             WorkoutManager.insertWorkout(context, workout, samples); |             new WorkoutSaver(context, workout, samples).saveWorkout(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -209,29 +199,30 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener | |||||||
|         if(isActive()){ |         if(isActive()){ | ||||||
|             double distance= 0; |             double distance= 0; | ||||||
|             if(getSampleCount() > 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){ |                 synchronized (samples){ | ||||||
|                     WorkoutSample lastSample= samples.get(samples.size() - 1); |                     WorkoutSample lastSample= samples.get(samples.size() - 1); | ||||||
|                     distance= LocationListener.locationToLatLong(location).sphericalDistance(new LatLong(lastSample.lat, lastSample.lon)); |                     distance= LocationListener.locationToLatLong(location).sphericalDistance(new LatLong(lastSample.lat, lastSample.lon)); | ||||||
|                     long timediff= lastSample.absoluteTime - location.getTime(); |                     long timediff= lastSample.absoluteTime - location.getTime(); | ||||||
|                     if(distance < getMinDistance(workout.workoutType) && timediff < 500){ |                     if (distance < workout.getWorkoutType().minDistance && timediff < 500) { | ||||||
|                         return; |                         return; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             lastSampleTime= System.currentTimeMillis(); |             lastSampleTime= System.currentTimeMillis(); | ||||||
|             if(state == RecordingState.RUNNING && location.getTime() > workout.start){ |             if(state == RecordingState.RUNNING && location.getTime() > workout.start){ | ||||||
|                 if(samples.size() == 2 && !hasBegan){ |                 if(samples.size() == 2 && !hasBegun){ | ||||||
|                     lastResume= System.currentTimeMillis(); |                     initialClearValues(); | ||||||
|                     workout.start= System.currentTimeMillis(); |                     hasBegun = true; // Do not clear a second time | ||||||
|                     lastPause= 0; |  | ||||||
|                     time= 0; |  | ||||||
|                     pauseTime= 0; |  | ||||||
|                     this.distance= 0; |  | ||||||
|                     samples.clear(); |  | ||||||
| 
 |  | ||||||
|                     hasBegan = true; // Do not clear a second time |  | ||||||
|                 } |                 } | ||||||
|                 this.distance+= distance; |                 this.distance+= distance; | ||||||
|  |                 addToSamples(location); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void addToSamples(Location location){ | ||||||
|         WorkoutSample sample= new WorkoutSample(); |         WorkoutSample sample= new WorkoutSample(); | ||||||
|         sample.lat= location.getLatitude(); |         sample.lat= location.getLatitude(); | ||||||
|         sample.lon= location.getLongitude(); |         sample.lon= location.getLongitude(); | ||||||
| @ -247,15 +238,19 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener | |||||||
|         synchronized (samples){ |         synchronized (samples){ | ||||||
|             samples.add(sample); |             samples.add(sample); | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     private void initialClearValues(){ | ||||||
|      * Returns the distance in meters |         lastResume= System.currentTimeMillis(); | ||||||
|      */ |         workout.start= System.currentTimeMillis(); | ||||||
|     public int getDistance(){ |         lastPause= 0; | ||||||
|  |         time= 0; | ||||||
|  |         pauseTime= 0; | ||||||
|  |         this.distance= 0; | ||||||
|  |         samples.clear(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getDistanceInMeters() { | ||||||
|         return (int)distance; |         return (int)distance; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -312,7 +307,7 @@ public class WorkoutRecorder implements LocationListener.LocationChangeListener | |||||||
|         SIGNAL_OKAY(Color.GREEN), |         SIGNAL_OKAY(Color.GREEN), | ||||||
|         SIGNAL_BAD(Color.YELLOW); |         SIGNAL_BAD(Color.YELLOW); | ||||||
| 
 | 
 | ||||||
|         public int color; |         public final int color; | ||||||
| 
 | 
 | ||||||
|         GpsState(int color) { |         GpsState(int color) { | ||||||
|             this.color = color; |             this.color = color; | ||||||
|  | |||||||
							
								
								
									
										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(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -17,25 +17,39 @@ | |||||||
|  *     along with this program.  If not, see <http://www.gnu.org/licenses/>. |  *     along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package de.tadris.fitness.util; | package de.tadris.fitness.recording.announcement; | ||||||
|  | 
 | ||||||
|  | import android.content.Context; | ||||||
| 
 | 
 | ||||||
| import de.tadris.fitness.R; | import de.tadris.fitness.R; | ||||||
| import de.tadris.fitness.data.Workout; | import de.tadris.fitness.recording.WorkoutRecorder; | ||||||
| 
 | 
 | ||||||
| public class ThemeManager { | public class AnnouncementGPSStatus extends Announcement { | ||||||
| 
 | 
 | ||||||
|     public static int getThemeByWorkoutType(String type){ |     public AnnouncementGPSStatus(Context context) { | ||||||
|         switch (type){ |         super(context); | ||||||
|             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 int getThemeByWorkout(Workout workout){ |     @Override | ||||||
|         return getThemeByWorkoutType(workout.workoutType); |     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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -17,17 +17,26 @@ | |||||||
|  *     along with this program.  If not, see <http://www.gnu.org/licenses/>. |  *     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[]{ |     ALWAYS, | ||||||
|             MapnikTileSource.INSTANCE, HumanitarianTileSource.INSTANCE, ThunderforestTileSource.OUTDOORS, ThunderforestTileSource.CYLE_MAP |     HEADPHONES; | ||||||
|     }; |  | ||||||
| 
 | 
 | ||||||
|     public enum Purpose{ |     static AnnouncementMode getCurrentMode(Context context) { | ||||||
|         DEFAULT, OUTDOOR, CYCLING |         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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -20,12 +20,13 @@ | |||||||
| package de.tadris.fitness.util; | package de.tadris.fitness.util; | ||||||
| 
 | 
 | ||||||
| import de.tadris.fitness.data.Workout; | import de.tadris.fitness.data.Workout; | ||||||
|  | import de.tadris.fitness.data.WorkoutType; | ||||||
| 
 | 
 | ||||||
| public class CalorieCalculator { | 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 workout the workout | ||||||
|      * @param weight the weight of the person in kilogram |      * @param weight the weight of the person in kilogram | ||||||
| @ -43,21 +44,18 @@ public class CalorieCalculator { | |||||||
|      * workoutType and avgSpeed of workout have to be set |      * workoutType and avgSpeed of workout have to be set | ||||||
|      * |      * | ||||||
|      * Calculation currently ignores height. |      * Calculation currently ignores height. | ||||||
|      * |  | ||||||
|      * @param workout |  | ||||||
|      * @return MET |      * @return MET | ||||||
|      */ |      */ | ||||||
|     public static double getMET(Workout workout){ |     private static double getMET(Workout workout) { | ||||||
|         double speedInKmh= workout.avgSpeed * 3.6; |         double speedInKmh= workout.avgSpeed * 3.6; | ||||||
|         if(workout.workoutType.equals(Workout.WORKOUT_TYPE_RUNNING) || workout.workoutType.equals(Workout.WORKOUT_TYPE_HIKING)){ |         WorkoutType type = workout.getWorkoutType(); | ||||||
|             // This is a linear graph based on the website linked above |         if (type == WorkoutType.RUNNING || type == WorkoutType.HIKING) { | ||||||
|             return Math.max(1.5, speedInKmh*1.117 - 2.1906); |             return Math.max(1.5, speedInKmh*1.117 - 2.1906); | ||||||
|         } |         } | ||||||
|         if(workout.workoutType.equals(Workout.WORKOUT_TYPE_CYCLING)){ |         if (type == WorkoutType.CYCLING) { | ||||||
|             // This is a linear graph based on the website linked above |  | ||||||
|             return Math.max(3, (speedInKmh-10) / 1.5); |             return Math.max(3, (speedInKmh-10) / 1.5); | ||||||
|         } |         } | ||||||
|         return -1; |         return 0; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -28,7 +28,7 @@ import de.tadris.fitness.R; | |||||||
| 
 | 
 | ||||||
| public class NotificationHelper { | public class NotificationHelper { | ||||||
| 
 | 
 | ||||||
|     public static String CHANNEL_WORKOUT= "workout"; |     public static final String CHANNEL_WORKOUT = "workout"; | ||||||
| 
 | 
 | ||||||
|     public static void createChannels(Context context){ |     public static void createChannels(Context context){ | ||||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |         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,132 +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.core.JsonParser; |  | ||||||
| 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.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.mapStyle= preferences.getMapStyle(); |  | ||||||
|         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, UnsupportedVersionException { |  | ||||||
|         listener.onStatusChanged(0, context.getString(R.string.loadingFile)); |  | ||||||
|         XmlMapper xmlMapper = new XmlMapper(); |  | ||||||
|         xmlMapper.configure(JsonParser.Feature.IGNORE_UNDEFINED, true); |  | ||||||
|         FitoTrackDataContainer container = xmlMapper.readValue(context.getContentResolver().openInputStream(input), FitoTrackDataContainer.class); |  | ||||||
| 
 |  | ||||||
|         if(container.version != 1){ |  | ||||||
|             throw new UnsupportedVersionException("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) |  | ||||||
|                 .putBoolean("firstStart", false).putString("mapStyle", container.settings.mapStyle) |  | ||||||
|                 .commit(); |  | ||||||
| 
 |  | ||||||
|         AppDatabase database= Instance.getInstance(context).db; |  | ||||||
| 
 |  | ||||||
|         database.runInTransaction(() -> { |  | ||||||
|             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,59 +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; |  | ||||||
| 
 |  | ||||||
| public class FitoTrackSettings { |  | ||||||
| 
 |  | ||||||
|     String preferredUnitSystem; |  | ||||||
|     int weight; |  | ||||||
|     String mapStyle; |  | ||||||
| 
 |  | ||||||
|     public FitoTrackSettings(){} |  | ||||||
| 
 |  | ||||||
|     public FitoTrackSettings(String preferredUnitSystem, int weight, String mapStyle) { |  | ||||||
|         this.preferredUnitSystem = preferredUnitSystem; |  | ||||||
|         this.weight = weight; |  | ||||||
|         this.mapStyle = mapStyle; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public String getMapStyle() { |  | ||||||
|         return mapStyle; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public void setMapStyle(String mapStyle) { |  | ||||||
|         this.mapStyle = mapStyle; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -37,7 +37,7 @@ public class Gpx { | |||||||
|     Metadata metadata; |     Metadata metadata; | ||||||
| 
 | 
 | ||||||
|     String name; |     String name; | ||||||
|     String desc; |     private String desc; | ||||||
| 
 | 
 | ||||||
|     @JacksonXmlElementWrapper(useWrapping = false) |     @JacksonXmlElementWrapper(useWrapping = false) | ||||||
|     List<Track> trk; |     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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -19,6 +19,7 @@ | |||||||
| 
 | 
 | ||||||
| package de.tadris.fitness.util.gpx; | package de.tadris.fitness.util.gpx; | ||||||
| 
 | 
 | ||||||
|  | import android.annotation.SuppressLint; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| 
 | 
 | ||||||
| import com.fasterxml.jackson.dataformat.xml.XmlMapper; | import com.fasterxml.jackson.dataformat.xml.XmlMapper; | ||||||
| @ -40,7 +41,7 @@ public class GpxExporter { | |||||||
|         mapper.writeValue(file, getGpxFromWorkout(context, workout)); |         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 gpx= new Gpx(); | ||||||
|         gpx.name= workout.toString(); |         gpx.name= workout.toString(); | ||||||
|         gpx.version= "1.1"; |         gpx.version= "1.1"; | ||||||
| @ -52,7 +53,7 @@ public class GpxExporter { | |||||||
|         return gpx; |         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); |         WorkoutSample[] samples= Instance.getInstance(context).db.workoutDao().getAllSamplesOfWorkout(workout.id); | ||||||
|         Track track= new Track(); |         Track track= new Track(); | ||||||
|         track.number= number; |         track.number= number; | ||||||
| @ -60,7 +61,7 @@ public class GpxExporter { | |||||||
|         track.cmt= workout.comment; |         track.cmt= workout.comment; | ||||||
|         track.desc= workout.comment; |         track.desc= workout.comment; | ||||||
|         track.src= "FitoTrack"; |         track.src= "FitoTrack"; | ||||||
|         track.type= workout.workoutType; |         track.type = workout.getWorkoutType().id; | ||||||
|         track.trkseg= new ArrayList<>(); |         track.trkseg= new ArrayList<>(); | ||||||
| 
 | 
 | ||||||
|         TrackSegment segment= new TrackSegment(); |         TrackSegment segment= new TrackSegment(); | ||||||
| @ -77,13 +78,14 @@ public class GpxExporter { | |||||||
|         return track; |         return track; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @SuppressLint("SimpleDateFormat") | ||||||
|     private static final SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); |     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)); |         return getDateTime(new Date(time)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static String getDateTime(Date date){ |     private static String getDateTime(Date date) { | ||||||
|         return formatter.format(date); |         return formatter.format(date); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -21,9 +21,9 @@ package de.tadris.fitness.util.gpx; | |||||||
| 
 | 
 | ||||||
| public class Metadata { | public class Metadata { | ||||||
| 
 | 
 | ||||||
|     String name; |     private String name; | ||||||
|     String desc; |     private String desc; | ||||||
|     String time; |     private String time; | ||||||
| 
 | 
 | ||||||
|     public Metadata() { |     public Metadata() { | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -21,21 +21,23 @@ package de.tadris.fitness.util.gpx; | |||||||
| 
 | 
 | ||||||
| import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; | ||||||
| 
 | 
 | ||||||
| public class TrackPoint { | class TrackPoint { | ||||||
| 
 | 
 | ||||||
|     @JacksonXmlProperty(isAttribute = true) |     @JacksonXmlProperty(isAttribute = true) | ||||||
|  |     private | ||||||
|     double lat; |     double lat; | ||||||
| 
 | 
 | ||||||
|     @JacksonXmlProperty(isAttribute = true) |     @JacksonXmlProperty(isAttribute = true) | ||||||
|  |     private | ||||||
|     double lon; |     double lon; | ||||||
| 
 | 
 | ||||||
|     double ele; |     private double ele; | ||||||
| 
 | 
 | ||||||
|     String time; |     private String time; | ||||||
| 
 | 
 | ||||||
|     String fix; |     private String fix; | ||||||
| 
 | 
 | ||||||
|     TrackPointExtension extensions; |     private TrackPointExtension extensions; | ||||||
| 
 | 
 | ||||||
|     public TrackPoint(){} |     public TrackPoint(){} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -21,7 +21,7 @@ package de.tadris.fitness.util.gpx; | |||||||
| 
 | 
 | ||||||
| public class TrackPointExtension { | public class TrackPointExtension { | ||||||
| 
 | 
 | ||||||
|     double speed; |     private double speed; | ||||||
| 
 | 
 | ||||||
|     public TrackPointExtension(){} |     public TrackPointExtension(){} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -23,7 +23,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; | |||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| public class TrackSegment { | class TrackSegment { | ||||||
| 
 | 
 | ||||||
|     @JacksonXmlElementWrapper(useWrapping = false) |     @JacksonXmlElementWrapper(useWrapping = false) | ||||||
|     List<TrackPoint> trkpt; |     List<TrackPoint> trkpt; | ||||||
|  | |||||||
| @ -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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -24,22 +24,23 @@ import android.preference.PreferenceManager; | |||||||
| 
 | 
 | ||||||
| public class UnitUtils { | public class UnitUtils { | ||||||
| 
 | 
 | ||||||
|     public static final Unit UNITS_METRIC= new Metric(); |     private static final Unit UNITS_METRIC = new Metric(); | ||||||
|     public static final Unit UNITS_METRIC_PHYSICAL= new MetricPhysical(); |     private static final Unit UNITS_METRIC_PHYSICAL = new MetricPhysical(); | ||||||
|     public static final Unit UNITS_IMPERIAL_YARDS= new Imperial(); |     private static final Unit UNITS_IMPERIAL_YARDS = new Imperial(); | ||||||
|     public static final Unit UNITS_IMPERIAL_METERS= new ImperialWithMeters(); |     private static final Unit UNITS_IMPERIAL_METERS = new ImperialWithMeters(); | ||||||
|     public static final Unit[] supportedUnits= new Unit[] { |     private static final Unit[] supportedUnits = new Unit[]{ | ||||||
|             UNITS_METRIC, UNITS_METRIC_PHYSICAL, UNITS_IMPERIAL_YARDS, UNITS_IMPERIAL_METERS |             UNITS_METRIC, UNITS_METRIC_PHYSICAL, UNITS_IMPERIAL_YARDS, UNITS_IMPERIAL_METERS | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     public static Unit CHOSEN_SYSTEM= UNITS_METRIC; |     public static Unit CHOSEN_SYSTEM= UNITS_METRIC; | ||||||
| 
 | 
 | ||||||
|     public static void setUnit(Context context){ |     public static void setUnit(Context context){ | ||||||
|         int id= Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(context).getString("unitSystem", String.valueOf(UnitUtils.UNITS_METRIC.getId()))); |         String id = PreferenceManager.getDefaultSharedPreferences(context).getString("unitSystem", String.valueOf(UnitUtils.UNITS_METRIC.getId())); | ||||||
|         setUnit(id); |         assert id != null; | ||||||
|  |         setUnit(Integer.parseInt(id)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static void setUnit(int id){ |     private static void setUnit(int id) { | ||||||
|         CHOSEN_SYSTEM= UNITS_METRIC; |         CHOSEN_SYSTEM= UNITS_METRIC; | ||||||
|         for(Unit unit : supportedUnits){ |         for(Unit unit : supportedUnits){ | ||||||
|             if(id == unit.getId()){ |             if(id == unit.getId()){ | ||||||
| @ -62,47 +63,38 @@ public class UnitUtils { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static String getHourMinuteSecondTime(long time){ |     public static String getHourMinuteSecondTime(long time){ | ||||||
|         long totalSeks= time / 1000; |         long totalSecs = time / 1000; | ||||||
|         long totalMins= totalSeks / 60; |         long totalMins = totalSecs / 60; | ||||||
|         long hours= totalMins / 60; |         long hours= totalMins / 60; | ||||||
|         long mins= totalMins % 60; |         long mins= totalMins % 60; | ||||||
|         long seks= totalSeks % 60; |         long secs = totalSecs % 60; | ||||||
|         String minStr= (mins < 10 ? "0" : "") + mins; |         String minStr= (mins < 10 ? "0" : "") + mins; | ||||||
|         String sekStr= (seks < 10 ? "0" : "") + seks; |         String sekStr = (secs < 10 ? "0" : "") + secs; | ||||||
|         return hours + ":" + minStr + ":" + sekStr; |         return hours + ":" + minStr + ":" + sekStr; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     public static String getPace(double metricPace) { | ||||||
|      * |  | ||||||
|      * @param pace Pace in min/km |  | ||||||
|      * @return Pace |  | ||||||
|      */ |  | ||||||
|     public static String getPace(double pace){ |  | ||||||
|         double one= CHOSEN_SYSTEM.getDistanceFromKilometers(1); |         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() |      *CHOSEN_SYSTEM.getLongDistanceUnit() | ||||||
|      * @param consumption consumption in kcal/km |      * @param consumption consumption in kcal/km | ||||||
|      * @return |  | ||||||
|      */ |      */ | ||||||
|     public static String getRelativeEnergyConsumption(double consumption){ |     public static String getRelativeEnergyConsumption(double consumption){ | ||||||
|         double one= CHOSEN_SYSTEM.getDistanceFromKilometers(1); |         double one= CHOSEN_SYSTEM.getDistanceFromKilometers(1); | ||||||
|         return round(consumption / one, 2) + " kcal/" + CHOSEN_SYSTEM.getLongDistanceUnit(); |         return round(consumption / one, 2) + " kcal/" + CHOSEN_SYSTEM.getLongDistanceUnit(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     public static String getDistance(int distanceInMeters) { | ||||||
|      * |         if (distanceInMeters >= 1000) { | ||||||
|      * @param distance Distance in meters |             return round(CHOSEN_SYSTEM.getDistanceFromKilometers((double) distanceInMeters / 1000d), 1) + " " + CHOSEN_SYSTEM.getLongDistanceUnit(); | ||||||
|      * @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(); |  | ||||||
|         }else{ |         }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(); |         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); |         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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -28,8 +28,8 @@ import de.tadris.fitness.R; | |||||||
| 
 | 
 | ||||||
| public class ProgressDialogController { | public class ProgressDialogController { | ||||||
| 
 | 
 | ||||||
|     private Activity context; |     private final Activity context; | ||||||
|     private Dialog dialog; |     private final Dialog dialog; | ||||||
|     private TextView infoView; |     private TextView infoView; | ||||||
|     private ProgressBar progressBar; |     private ProgressBar progressBar; | ||||||
| 
 | 
 | ||||||
| @ -38,7 +38,7 @@ public class ProgressDialogController { | |||||||
|         setTitle(title); |         setTitle(title); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public ProgressDialogController(Activity context) { |     private ProgressDialogController(Activity context) { | ||||||
|         this.context = context; |         this.context = context; | ||||||
|         this.dialog= new Dialog(context); |         this.dialog= new Dialog(context); | ||||||
|         initDialog(); |         initDialog(); | ||||||
| @ -51,7 +51,7 @@ public class ProgressDialogController { | |||||||
|         progressBar= dialog.findViewById(R.id.dialogProgressBar); |         progressBar= dialog.findViewById(R.id.dialogProgressBar); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void setTitle(String title){ |     private void setTitle(String title) { | ||||||
|         dialog.setTitle(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 |  * This file is part of FitoTrack | ||||||
|  * |  * | ||||||
| @ -32,7 +32,6 @@ import java.util.Date; | |||||||
| 
 | 
 | ||||||
| import de.tadris.fitness.R; | import de.tadris.fitness.R; | ||||||
| import de.tadris.fitness.data.Workout; | import de.tadris.fitness.data.Workout; | ||||||
| import de.tadris.fitness.util.WorkoutTypeCalculator; |  | ||||||
| import de.tadris.fitness.util.unit.UnitUtils; | import de.tadris.fitness.util.unit.UnitUtils; | ||||||
| 
 | 
 | ||||||
| public class WorkoutAdapter extends RecyclerView.Adapter<WorkoutAdapter.WorkoutViewHolder>{ | 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{ |     public static class WorkoutViewHolder extends RecyclerView.ViewHolder{ | ||||||
| 
 | 
 | ||||||
|         View root; |         final View root; | ||||||
|         TextView lengthText, timeText, dateText, typeText, commentText; |         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); |             super(itemView); | ||||||
|             this.root= itemView; |             this.root= itemView; | ||||||
|             lengthText= itemView.findViewById(R.id.workoutLength); |             lengthText= itemView.findViewById(R.id.workoutLength); | ||||||
| @ -54,8 +57,8 @@ public class WorkoutAdapter extends RecyclerView.Adapter<WorkoutAdapter.WorkoutV | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Workout[] workouts; |     private final Workout[] workouts; | ||||||
|     WorkoutAdapterListener listener; |     private final WorkoutAdapterListener listener; | ||||||
| 
 | 
 | ||||||
|     public WorkoutAdapter(Workout[] workouts, WorkoutAdapterListener listener) { |     public WorkoutAdapter(Workout[] workouts, WorkoutAdapterListener listener) { | ||||||
|         this.workouts = workouts; |         this.workouts = workouts; | ||||||
| @ -74,7 +77,7 @@ public class WorkoutAdapter extends RecyclerView.Adapter<WorkoutAdapter.WorkoutV | |||||||
|     public void onBindViewHolder(WorkoutViewHolder holder, final int position) { |     public void onBindViewHolder(WorkoutViewHolder holder, final int position) { | ||||||
|         Workout workout= workouts[position]; |         Workout workout= workouts[position]; | ||||||
|         holder.dateText.setText(SimpleDateFormat.getDateTimeInstance().format(new Date(workout.start))); |         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 != null){ | ||||||
|             if(workout.comment.length() > 33){ |             if(workout.comment.length() > 33){ | ||||||
|                 holder.commentText.setText(workout.comment.substring(0, 30) + "..."); |                 holder.commentText.setText(workout.comment.substring(0, 30) + "..."); | ||||||
| @ -86,7 +89,7 @@ public class WorkoutAdapter extends RecyclerView.Adapter<WorkoutAdapter.WorkoutV | |||||||
|         } |         } | ||||||
|         holder.lengthText.setText(UnitUtils.getDistance(workout.length)); |         holder.lengthText.setText(UnitUtils.getDistance(workout.length)); | ||||||
|         holder.timeText.setText(UnitUtils.getHourMinuteTime(workout.duration)); |         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 -> { |         holder.root.setOnLongClickListener(v -> { | ||||||
|             listener.onItemLongClick(position, workout); |             listener.onItemLongClick(position, workout); | ||||||
|             return true; |             return true; | ||||||
| @ -100,7 +103,7 @@ public class WorkoutAdapter extends RecyclerView.Adapter<WorkoutAdapter.WorkoutV | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public interface WorkoutAdapterListener{ |     public interface WorkoutAdapterListener{ | ||||||
|         void onItemClick(Workout workout); |         void onItemClick(int pos, Workout workout); | ||||||
|         void onItemLongClick(int pos, Workout workout); |         void onItemLongClick(int pos, Workout workout); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
| @ -20,5 +19,7 @@ | |||||||
| 
 | 
 | ||||||
| <set xmlns:android="http://schemas.android.com/apk/res/android" | <set xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     android:duration="300"> |     android:duration="300"> | ||||||
|     <alpha android:fromAlpha="0" android:toAlpha="1"/> |     <alpha | ||||||
|  |         android:fromAlpha="0" | ||||||
|  |         android:toAlpha="1" /> | ||||||
| </set> | </set> | ||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
|  | |||||||
| @ -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 |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
| @ -20,10 +20,10 @@ | |||||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     android:width="24dp" |     android:width="24dp" | ||||||
|     android:height="24dp" |     android:height="24dp" | ||||||
|  |     android:tint="#FFFFFF" | ||||||
|     android:viewportWidth="24" |     android:viewportWidth="24" | ||||||
|     android:viewportHeight="24" |     android:viewportHeight="24"> | ||||||
|     android:tint="#FFFFFF"> |  | ||||||
|     <path |     <path | ||||||
|         android:fillColor="#FF000000" |         android:fillColor="#FF000000" | ||||||
|         android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/> |         android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" /> | ||||||
| </vector> | </vector> | ||||||
|  | |||||||
| @ -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 |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
| @ -20,11 +20,11 @@ | |||||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     android:width="24dp" |     android:width="24dp" | ||||||
|     android:height="24dp" |     android:height="24dp" | ||||||
|     android:viewportWidth="24" |     android:alpha="0.8" | ||||||
|     android:viewportHeight="24" |  | ||||||
|     android:tint="#FFFFFF" |     android:tint="#FFFFFF" | ||||||
|     android:alpha="0.8"> |     android:viewportWidth="24" | ||||||
|  |     android:viewportHeight="24"> | ||||||
|     <path |     <path | ||||||
|         android:fillColor="#FF000000" |         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"/> |         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> | </vector> | ||||||
|  | |||||||
| @ -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 |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
| @ -23,6 +23,6 @@ | |||||||
|     android:viewportWidth="24.0" |     android:viewportWidth="24.0" | ||||||
|     android:viewportHeight="24.0"> |     android:viewportHeight="24.0"> | ||||||
|     <path |     <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> | </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"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
| @ -19,10 +18,10 @@ | |||||||
|   --> |   --> | ||||||
| 
 | 
 | ||||||
| <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | <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" |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|     android:layout_width="match_parent" |     android:layout_width="match_parent" | ||||||
|     android:layout_height="match_parent" |     android:layout_height="match_parent" | ||||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" |  | ||||||
|     tools:context=".activity.ListWorkoutsActivity"> |     tools:context=".activity.ListWorkoutsActivity"> | ||||||
| 
 | 
 | ||||||
|     <androidx.recyclerview.widget.RecyclerView |     <androidx.recyclerview.widget.RecyclerView | ||||||
| @ -36,12 +35,12 @@ | |||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:layout_gravity="bottom|right" |         android:layout_gravity="bottom|right" | ||||||
|         android:layout_margin="10dp" |         android:layout_margin="10dp" | ||||||
|  |         app:menu_animationDelayPerItem="50" | ||||||
|         app:menu_colorNormal="@color/colorPrimary" |         app:menu_colorNormal="@color/colorPrimary" | ||||||
|         app:menu_colorPressed="@color/colorPrimaryDark" |         app:menu_colorPressed="@color/colorPrimaryDark" | ||||||
|         app:menu_animationDelayPerItem="50" |  | ||||||
|         app:menu_icon="@drawable/fab_add" |         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 |         <com.github.clans.fab.FloatingActionButton | ||||||
|             android:id="@+id/workoutListRecordRunning" |             android:id="@+id/workoutListRecordRunning" | ||||||
| @ -51,7 +50,7 @@ | |||||||
|             app:fab_colorNormal="@color/colorPrimaryRunning" |             app:fab_colorNormal="@color/colorPrimaryRunning" | ||||||
|             app:fab_colorPressed="@color/colorPrimaryDarkRunning" |             app:fab_colorPressed="@color/colorPrimaryDarkRunning" | ||||||
|             app:fab_label="@string/workoutTypeRunning" |             app:fab_label="@string/workoutTypeRunning" | ||||||
|             app:fab_size="normal"/> |             app:fab_size="normal" /> | ||||||
| 
 | 
 | ||||||
|         <com.github.clans.fab.FloatingActionButton |         <com.github.clans.fab.FloatingActionButton | ||||||
|             android:id="@+id/workoutListRecordHiking" |             android:id="@+id/workoutListRecordHiking" | ||||||
| @ -71,7 +70,17 @@ | |||||||
|             app:fab_colorNormal="@color/colorPrimaryBicycling" |             app:fab_colorNormal="@color/colorPrimaryBicycling" | ||||||
|             app:fab_colorPressed="@color/colorPrimaryDarkBicycling" |             app:fab_colorPressed="@color/colorPrimaryDarkBicycling" | ||||||
|             app:fab_label="@string/workoutTypeCycling" |             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> |     </com.github.clans.fab.FloatingActionMenu> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
| @ -32,7 +31,7 @@ | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         <LinearLayout |         <LinearLayout | ||||||
|             android:id="@+id/recordMapViewrRoot" |             android:id="@+id/recordMapViewerRoot" | ||||||
|             android:layout_width="match_parent" |             android:layout_width="match_parent" | ||||||
|             android:layout_height="match_parent" |             android:layout_height="match_parent" | ||||||
|             android:orientation="vertical"> |             android:orientation="vertical"> | ||||||
| @ -56,6 +55,15 @@ | |||||||
|             android:text="@string/gps" |             android:text="@string/gps" | ||||||
|             android:textColor="@android:color/transparent" /> |             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> |     </FrameLayout> | ||||||
| 
 | 
 | ||||||
| @ -65,8 +73,8 @@ | |||||||
|         android:layout_height="200dp" |         android:layout_height="200dp" | ||||||
|         android:layout_alignParentBottom="true" |         android:layout_alignParentBottom="true" | ||||||
|         android:layout_marginBottom="0dp" |         android:layout_marginBottom="0dp" | ||||||
|         android:padding="10dp" |         android:orientation="vertical" | ||||||
|         android:orientation="vertical"> |         android:padding="10dp"> | ||||||
| 
 | 
 | ||||||
|         <TextView |         <TextView | ||||||
|             android:id="@+id/recordTime" |             android:id="@+id/recordTime" | ||||||
| @ -75,7 +83,7 @@ | |||||||
|             android:fontFamily="sans-serif-black" |             android:fontFamily="sans-serif-black" | ||||||
|             android:text="0:44:08" |             android:text="0:44:08" | ||||||
|             android:textAlignment="center" |             android:textAlignment="center" | ||||||
|             android:textColor="@android:color/black" |             android:textColor="?android:textColorPrimary" | ||||||
|             android:textSize="30sp" |             android:textSize="30sp" | ||||||
|             android:textStyle="bold" /> |             android:textStyle="bold" /> | ||||||
| 
 | 
 | ||||||
| @ -117,7 +125,7 @@ | |||||||
|                     android:text="2,06 km" |                     android:text="2,06 km" | ||||||
|                     android:textAlignment="center" |                     android:textAlignment="center" | ||||||
|                     android:textAllCaps="false" |                     android:textAllCaps="false" | ||||||
|                     android:textColor="@android:color/black" |                     android:textColor="?android:textColorPrimary" | ||||||
|                     android:textSize="24sp" |                     android:textSize="24sp" | ||||||
|                     android:textStyle="bold" /> |                     android:textStyle="bold" /> | ||||||
| 
 | 
 | ||||||
| @ -137,7 +145,7 @@ | |||||||
|                     android:text="30 kcal" |                     android:text="30 kcal" | ||||||
|                     android:textAlignment="center" |                     android:textAlignment="center" | ||||||
|                     android:textAllCaps="false" |                     android:textAllCaps="false" | ||||||
|                     android:textColor="@android:color/black" |                     android:textColor="?android:textColorPrimary" | ||||||
|                     android:textSize="24sp" |                     android:textSize="24sp" | ||||||
|                     android:textStyle="bold" /> |                     android:textStyle="bold" /> | ||||||
| 
 | 
 | ||||||
| @ -156,7 +164,7 @@ | |||||||
|                     android:id="@+id/recordInfo3Title" |                     android:id="@+id/recordInfo3Title" | ||||||
|                     android:layout_width="match_parent" |                     android:layout_width="match_parent" | ||||||
|                     android:layout_height="wrap_content" |                     android:layout_height="wrap_content" | ||||||
|                     android:text="@string/workoutAvgSpeed" |                     android:text="@string/workoutAvgSpeedShort" | ||||||
|                     android:textAlignment="center" |                     android:textAlignment="center" | ||||||
|                     android:textAllCaps="true" |                     android:textAllCaps="true" | ||||||
|                     android:textStyle="bold" /> |                     android:textStyle="bold" /> | ||||||
| @ -168,7 +176,7 @@ | |||||||
|                     android:text="7 km/h" |                     android:text="7 km/h" | ||||||
|                     android:textAlignment="center" |                     android:textAlignment="center" | ||||||
|                     android:textAllCaps="false" |                     android:textAllCaps="false" | ||||||
|                     android:textColor="@android:color/black" |                     android:textColor="?android:textColorPrimary" | ||||||
|                     android:textSize="24sp" |                     android:textSize="24sp" | ||||||
|                     android:textStyle="bold" /> |                     android:textStyle="bold" /> | ||||||
| 
 | 
 | ||||||
| @ -188,7 +196,7 @@ | |||||||
|                     android:text="30 kcal" |                     android:text="30 kcal" | ||||||
|                     android:textAlignment="center" |                     android:textAlignment="center" | ||||||
|                     android:textAllCaps="false" |                     android:textAllCaps="false" | ||||||
|                     android:textColor="@android:color/black" |                     android:textColor="?android:textColorPrimary" | ||||||
|                     android:textSize="24sp" |                     android:textSize="24sp" | ||||||
|                     android:textStyle="bold" /> |                     android:textStyle="bold" /> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
| @ -35,7 +34,7 @@ | |||||||
|             android:layout_width="match_parent" |             android:layout_width="match_parent" | ||||||
|             android:layout_height="wrap_content" |             android:layout_height="wrap_content" | ||||||
|             android:orientation="vertical" |             android:orientation="vertical" | ||||||
|             android:padding="20dp"></LinearLayout> |             android:padding="20dp" /> | ||||||
| 
 | 
 | ||||||
|     </ScrollView> |     </ScrollView> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
| @ -25,7 +24,7 @@ | |||||||
|     android:layout_width="match_parent" |     android:layout_width="match_parent" | ||||||
|     android:layout_height="match_parent" |     android:layout_height="match_parent" | ||||||
|     android:orientation="vertical" |     android:orientation="vertical" | ||||||
|     tools:context=".activity.ShowWorkoutMapActivity" > |     tools:context=".activity.ShowWorkoutMapActivity"> | ||||||
| 
 | 
 | ||||||
|     <androidx.constraintlayout.widget.Guideline |     <androidx.constraintlayout.widget.Guideline | ||||||
|         android:id="@+id/guideline4" |         android:id="@+id/guideline4" | ||||||
| @ -58,6 +57,6 @@ | |||||||
|         app:layout_constraintBottom_toBottomOf="parent" |         app:layout_constraintBottom_toBottomOf="parent" | ||||||
|         app:layout_constraintEnd_toEndOf="parent" |         app:layout_constraintEnd_toEndOf="parent" | ||||||
|         app:layout_constraintStart_toStartOf="parent" |         app:layout_constraintStart_toStartOf="parent" | ||||||
|         app:layout_constraintTop_toTopOf="@+id/guideline4"></LinearLayout> |         app:layout_constraintTop_toTopOf="@+id/guideline4" /> | ||||||
| 
 | 
 | ||||||
| </android.support.constraint.ConstraintLayout> | </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,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
|  | |||||||
							
								
								
									
										40
									
								
								app/src/main/res/layout/dialog_spoken_updates_picker.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								app/src/main/res/layout/dialog_spoken_updates_picker.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <!-- | ||||||
|  |   ~ Copyright (c) 2020 Ruslan Sokolovski <russok@gmail.com> | ||||||
|  |   ~ | ||||||
|  |   ~ This file is part of FitoTrack | ||||||
|  |   ~ | ||||||
|  |   ~ FitoTrack is free software: you can redistribute it and/or modify | ||||||
|  |   ~     it under the terms of the GNU General Public License as published by | ||||||
|  |   ~     the Free Software Foundation, either version 3 of the License, or | ||||||
|  |   ~     (at your option) any later version. | ||||||
|  |   ~ | ||||||
|  |   ~     FitoTrack is distributed in the hope that it will be useful, | ||||||
|  |   ~     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |   ~     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |   ~     GNU General Public License for more details. | ||||||
|  |   ~ | ||||||
|  |   ~     You should have received a copy of the GNU General Public License | ||||||
|  |   ~     along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |   --> | ||||||
|  | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:layout_width="fill_parent" | ||||||
|  |     android:layout_height="fill_parent" > | ||||||
|  | 
 | ||||||
|  |    <NumberPicker | ||||||
|  |         android:id="@+id/spokenUpdatesTimePicker" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_alignParentTop="true" | ||||||
|  |         android:layout_margin="50dp" | ||||||
|  |         android:layout_alignParentLeft="true"/> | ||||||
|  | 
 | ||||||
|  |    <NumberPicker | ||||||
|  |         android:id="@+id/spokenUpdatesDistancePicker" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_alignParentTop="true" | ||||||
|  |         android:layout_margin="50dp" | ||||||
|  |         android:layout_alignParentRight="true"/> | ||||||
|  | 
 | ||||||
|  | </RelativeLayout> | ||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
| @ -29,11 +28,12 @@ | |||||||
| 
 | 
 | ||||||
|         <TableRow |         <TableRow | ||||||
|             android:layout_width="match_parent" |             android:layout_width="match_parent" | ||||||
|             android:layout_height="match_parent" > |             android:layout_height="match_parent"> | ||||||
| 
 | 
 | ||||||
|             <TextView |             <TextView | ||||||
|                 android:layout_width="wrap_content" |                 android:layout_width="wrap_content" | ||||||
|                 android:layout_height="wrap_content" |                 android:layout_height="wrap_content" | ||||||
|  |                 android:labelFor="@id/uploadDescription" | ||||||
|                 android:text="@string/description" /> |                 android:text="@string/description" /> | ||||||
| 
 | 
 | ||||||
|             <EditText |             <EditText | ||||||
| @ -48,7 +48,7 @@ | |||||||
| 
 | 
 | ||||||
|         <TableRow |         <TableRow | ||||||
|             android:layout_width="match_parent" |             android:layout_width="match_parent" | ||||||
|             android:layout_height="match_parent" > |             android:layout_height="match_parent"> | ||||||
| 
 | 
 | ||||||
|             <TextView |             <TextView | ||||||
|                 android:layout_width="wrap_content" |                 android:layout_width="wrap_content" | ||||||
| @ -64,13 +64,6 @@ | |||||||
|                 android:spinnerMode="dropdown" /> |                 android:spinnerMode="dropdown" /> | ||||||
|         </TableRow> |         </TableRow> | ||||||
| 
 | 
 | ||||||
|         <TableRow |  | ||||||
|             android:layout_width="match_parent" |  | ||||||
|             android:layout_height="match_parent" /> |  | ||||||
| 
 |  | ||||||
|         <TableRow |  | ||||||
|             android:layout_width="match_parent" |  | ||||||
|             android:layout_height="match_parent" /> |  | ||||||
|     </TableLayout> |     </TableLayout> | ||||||
| 
 | 
 | ||||||
|     <CheckBox |     <CheckBox | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
| @ -20,7 +19,7 @@ | |||||||
| 
 | 
 | ||||||
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     android:layout_width="fill_parent" |     android:layout_width="fill_parent" | ||||||
|     android:layout_height="fill_parent" > |     android:layout_height="fill_parent"> | ||||||
| 
 | 
 | ||||||
|     <NumberPicker |     <NumberPicker | ||||||
|         android:id="@+id/weightPicker" |         android:id="@+id/weightPicker" | ||||||
|  | |||||||
							
								
								
									
										71
									
								
								app/src/main/res/layout/enter_workout_line.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								app/src/main/res/layout/enter_workout_line.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | |||||||
|  | <?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" | ||||||
|  |     android:orientation="vertical" | ||||||
|  |     android:layout_marginLeft="-15dp" | ||||||
|  |     android:layout_marginRight="-15dp" | ||||||
|  |     android:gravity="center_vertical" | ||||||
|  |     android:background="?android:selectableItemBackground"> | ||||||
|  | 
 | ||||||
|  |     <RelativeLayout | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="50dp" | ||||||
|  |         android:paddingStart="15dp" | ||||||
|  |         android:paddingEnd="15dp"> | ||||||
|  | 
 | ||||||
|  |         <TextView | ||||||
|  |             android:id="@+id/lineKey" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:layout_alignParentLeft="true" | ||||||
|  |             android:layout_centerVertical="true" | ||||||
|  |             android:textAppearance="@android:style/TextAppearance.Material.Medium" /> | ||||||
|  | 
 | ||||||
|  |         <LinearLayout | ||||||
|  |             android:id="@+id/lineViewRoot" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:layout_toStartOf="@+id/lineValue" | ||||||
|  |             android:layout_toEndOf="@+id/lineKey" | ||||||
|  |             android:layout_centerVertical="true" | ||||||
|  |             android:gravity="right|center_vertical" | ||||||
|  |             android:orientation="horizontal"> | ||||||
|  | 
 | ||||||
|  |         </LinearLayout> | ||||||
|  | 
 | ||||||
|  |         <TextView | ||||||
|  |             android:id="@+id/lineValue" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:layout_alignParentRight="true" | ||||||
|  |             android:layout_centerVertical="true" | ||||||
|  |             android:textAlignment="textEnd" | ||||||
|  |             android:textAppearance="@android:style/TextAppearance.Material.Medium" /> | ||||||
|  | 
 | ||||||
|  |     </RelativeLayout> | ||||||
|  | 
 | ||||||
|  |     <View | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="1dp" | ||||||
|  |         android:background="?android:dividerHorizontal" /> | ||||||
|  | 
 | ||||||
|  | </LinearLayout> | ||||||
| @ -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/>. | ||||||
|  |   --> | ||||||
|  | 
 | ||||||
|  | <CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:id="@android:id/text1" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:drawableStart="?android:attr/listChoiceIndicatorSingle" | ||||||
|  |     android:drawablePadding="20dp" | ||||||
|  |     android:ellipsize="marquee" | ||||||
|  |     android:gravity="center_vertical" | ||||||
|  |     android:minHeight="48dip" | ||||||
|  |     android:paddingStart="20dp" | ||||||
|  |     android:paddingEnd="24dp" | ||||||
|  |     android:textAppearance="@android:style/TextAppearance.Material.Medium" | ||||||
|  |     android:textColor="?android:attr/textColorAlertDialogListItem" /> | ||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
| @ -42,7 +41,7 @@ | |||||||
|             android:fontFamily="sans-serif-black" |             android:fontFamily="sans-serif-black" | ||||||
|             android:text="" |             android:text="" | ||||||
|             android:textAllCaps="true" |             android:textAllCaps="true" | ||||||
|             android:textColor="@color/textColorLight" |             android:textColor="?android:textColorSecondary" | ||||||
|             android:textStyle="bold" /> |             android:textStyle="bold" /> | ||||||
| 
 | 
 | ||||||
|         <TextView |         <TextView | ||||||
| @ -52,7 +51,7 @@ | |||||||
|             android:fontFamily="sans-serif-black" |             android:fontFamily="sans-serif-black" | ||||||
|             android:text="" |             android:text="" | ||||||
|             android:textAllCaps="false" |             android:textAllCaps="false" | ||||||
|             android:textColor="@color/textLighterBlack" |             android:textColor="?android:textColorPrimary" | ||||||
|             android:textSize="24sp" |             android:textSize="24sp" | ||||||
|             android:textStyle="bold" /> |             android:textStyle="bold" /> | ||||||
|     </LinearLayout> |     </LinearLayout> | ||||||
| @ -74,7 +73,7 @@ | |||||||
|             android:fontFamily="sans-serif-black" |             android:fontFamily="sans-serif-black" | ||||||
|             android:text="" |             android:text="" | ||||||
|             android:textAllCaps="true" |             android:textAllCaps="true" | ||||||
|             android:textColor="@color/textColorLight" |             android:textColor="?android:textColorSecondary" | ||||||
|             android:textStyle="bold" /> |             android:textStyle="bold" /> | ||||||
| 
 | 
 | ||||||
|         <TextView |         <TextView | ||||||
| @ -84,7 +83,7 @@ | |||||||
|             android:fontFamily="sans-serif-black" |             android:fontFamily="sans-serif-black" | ||||||
|             android:text="" |             android:text="" | ||||||
|             android:textAllCaps="false" |             android:textAllCaps="false" | ||||||
|             android:textColor="@color/textLighterBlack" |             android:textColor="?android:textColorPrimary" | ||||||
|             android:textSize="24sp" |             android:textSize="24sp" | ||||||
|             android:textStyle="bold" /> |             android:textStyle="bold" /> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
| @ -21,8 +20,8 @@ | |||||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     android:layout_width="match_parent" |     android:layout_width="match_parent" | ||||||
|     android:layout_height="wrap_content" |     android:layout_height="wrap_content" | ||||||
|     android:orientation="vertical" |     android:background="?android:selectableItemBackground" | ||||||
|     android:background="?android:selectableItemBackground"> |     android:orientation="vertical"> | ||||||
| 
 | 
 | ||||||
|     <LinearLayout |     <LinearLayout | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
| @ -37,7 +36,7 @@ | |||||||
|             android:layout_weight="1" |             android:layout_weight="1" | ||||||
|             android:text="Aug 19, 2019 20:15" |             android:text="Aug 19, 2019 20:15" | ||||||
|             android:textAllCaps="true" |             android:textAllCaps="true" | ||||||
|             android:textColor="@color/textLighterBlack" |             android:textColor="?android:textColorPrimary" | ||||||
|             android:textSize="18sp" |             android:textSize="18sp" | ||||||
|             android:textStyle="bold" /> |             android:textStyle="bold" /> | ||||||
| 
 | 
 | ||||||
| @ -81,7 +80,7 @@ | |||||||
|                 android:layout_width="match_parent" |                 android:layout_width="match_parent" | ||||||
|                 android:layout_height="wrap_content" |                 android:layout_height="wrap_content" | ||||||
|                 android:text="27min" |                 android:text="27min" | ||||||
|                 android:textColor="@color/textLighterBlack" |                 android:textColor="?android:textColorPrimary" | ||||||
|                 android:textSize="30sp" /> |                 android:textSize="30sp" /> | ||||||
|         </LinearLayout> |         </LinearLayout> | ||||||
| 
 | 
 | ||||||
| @ -90,6 +89,6 @@ | |||||||
|     <View |     <View | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|         android:layout_height="1dp" |         android:layout_height="1dp" | ||||||
|         android:background="@android:color/darker_gray"/> |         android:background="@android:color/darker_gray" /> | ||||||
| 
 | 
 | ||||||
| </LinearLayout> | </LinearLayout> | ||||||
							
								
								
									
										27
									
								
								app/src/main/res/menu/enter_workout_menu.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/src/main/res/menu/enter_workout_menu.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | <?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/>. | ||||||
|  |   --> | ||||||
|  | 
 | ||||||
|  | <menu xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  | 
 | ||||||
|  |     <item | ||||||
|  |         android:id="@+id/actionEnterWorkoutAdd" | ||||||
|  |         android:icon="@drawable/ic_add_white" | ||||||
|  |         android:showAsAction="always" | ||||||
|  |         android:title="@string/save" /> | ||||||
|  | </menu> | ||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
| @ -20,6 +19,9 @@ | |||||||
| 
 | 
 | ||||||
| <menu xmlns:android="http://schemas.android.com/apk/res/android"> | <menu xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
| 
 | 
 | ||||||
|  |     <item | ||||||
|  |         android:id="@+id/actionEditComment" | ||||||
|  |         android:title="@string/action_edit_comment" /> | ||||||
|     <item |     <item | ||||||
|         android:id="@+id/actionExportGpx" |         android:id="@+id/actionExportGpx" | ||||||
|         android:title="@string/exportAsGpxFile" /> |         android:title="@string/exportAsGpxFile" /> | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
|  | |||||||
							
								
								
									
										65341
									
								
								app/src/main/res/raw/geoids.csv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65341
									
								
								app/src/main/res/raw/geoids.csv
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										27
									
								
								app/src/main/res/values-de/announcement_mode.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/src/main/res/values-de/announcement_mode.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | <?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/>. | ||||||
|  |   --> | ||||||
|  | 
 | ||||||
|  | <resources> | ||||||
|  | 
 | ||||||
|  |     <string-array name="pref_announcement_mode"> | ||||||
|  |         <item>Immer</item> | ||||||
|  |         <item>Nur mit Kopfhörern</item> | ||||||
|  |     </string-array> | ||||||
|  | 
 | ||||||
|  | </resources> | ||||||
| @ -1,6 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?><!-- | ||||||
| <!-- |   ~ Copyright (c) 2020 Jannis Scheibe <jannis@tadris.de> | ||||||
|   ~ Copyright (c) 2019 Jannis Scheibe <jannis@tadris.de> |  | ||||||
|   ~ |   ~ | ||||||
|   ~ This file is part of FitoTrack |   ~ This file is part of FitoTrack | ||||||
|   ~ |   ~ | ||||||
|  | |||||||
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