├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── google-services.json ├── libs │ └── antpluginlib_3-5-0.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── trainerdb │ │ └── trainerandroid │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── trainerdb │ │ │ └── trainerandroid │ │ │ ├── AutoResizeTextView.java │ │ │ ├── Compression.java │ │ │ ├── IGetAsyncListener.java │ │ │ ├── TrainApplication.java │ │ │ ├── TrainWorkoutView.java │ │ │ ├── WorkoutView.java │ │ │ ├── activities │ │ │ ├── AppCompatPreferenceActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── SettingsActivity.java │ │ │ ├── TrainActivity.java │ │ │ ├── TrainFileListActivity.java │ │ │ ├── WorkoutComparator.java │ │ │ ├── WorkoutDetailActivity.java │ │ │ ├── WorkoutDetailFragment.java │ │ │ ├── WorkoutFilterActivity.java │ │ │ ├── WorkoutListActivity.java │ │ │ ├── WorkoutListItem.java │ │ │ ├── WorkoutListOptions.java │ │ │ └── WorkoutListParent.java │ │ │ ├── data │ │ │ ├── ErgFile.java │ │ │ ├── ErgPoint.java │ │ │ ├── LapPoint.java │ │ │ ├── PowerZone.java │ │ │ ├── RealtimeController.java │ │ │ ├── TextPoint.java │ │ │ ├── Workout.java │ │ │ ├── WorkoutData.java │ │ │ └── WorkoutService.java │ │ │ ├── listeners │ │ │ ├── Ant.java │ │ │ ├── AntCadence.java │ │ │ ├── AntHeartRate.java │ │ │ ├── AntPlusController.java │ │ │ └── AntSpeedDistance.java │ │ │ └── train │ │ │ ├── DeviceConfiguration.java │ │ │ ├── DeviceConfigurations.java │ │ │ ├── LapControl.java │ │ │ ├── NormalizedPower.java │ │ │ ├── NullController.java │ │ │ ├── RealtimeData.java │ │ │ ├── Rolling.java │ │ │ ├── StopWatch.java │ │ │ ├── TelemetryEnum.java │ │ │ ├── TelemetryListener.java │ │ │ ├── TelemetryView.java │ │ │ ├── TrainController.java │ │ │ ├── TrainFile.java │ │ │ └── TrainState.java │ └── res │ │ ├── drawable-hdpi │ │ ├── ic_info_black_24dp.png │ │ ├── ic_notifications_black_24dp.png │ │ └── ic_sync_black_24dp.png │ │ ├── drawable-mdpi │ │ ├── ic_info_black_24dp.png │ │ ├── ic_notifications_black_24dp.png │ │ └── ic_sync_black_24dp.png │ │ ├── drawable-v21 │ │ ├── ic_info_black_24dp.xml │ │ ├── ic_menu_camera.xml │ │ ├── ic_menu_gallery.xml │ │ ├── ic_menu_manage.xml │ │ ├── ic_menu_send.xml │ │ ├── ic_menu_share.xml │ │ ├── ic_menu_slideshow.xml │ │ ├── ic_notifications_black_24dp.xml │ │ └── ic_sync_black_24dp.xml │ │ ├── drawable-xhdpi │ │ ├── ic_info_black_24dp.png │ │ ├── ic_notifications_black_24dp.png │ │ └── ic_sync_black_24dp.png │ │ ├── drawable-xxhdpi │ │ ├── ic_info_black_24dp.png │ │ ├── ic_notifications_black_24dp.png │ │ └── ic_sync_black_24dp.png │ │ ├── drawable-xxxhdpi │ │ ├── ic_info_black_24dp.png │ │ ├── ic_notifications_black_24dp.png │ │ └── ic_sync_black_24dp.png │ │ ├── drawable │ │ └── side_nav_bar.xml │ │ ├── layout-land │ │ ├── activity_train.xml │ │ └── view_telemetry.xml │ │ ├── layout-w900dp │ │ └── workout_list.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_train.xml │ │ ├── activity_train_dialog.xml │ │ ├── activity_train_file_list.xml │ │ ├── activity_workout_detail.xml │ │ ├── activity_workout_filter.xml │ │ ├── activity_workout_list.xml │ │ ├── app_bar_main.xml │ │ ├── content_main.xml │ │ ├── nav_header_main.xml │ │ ├── train_file_list_content.xml │ │ ├── view_telemetry.xml │ │ ├── workout_detail.xml │ │ ├── workout_filter_group.xml │ │ ├── workout_filter_list_content.xml │ │ ├── workout_filter_sort_content.xml │ │ ├── workout_list.xml │ │ └── workout_list_content.xml │ │ ├── menu │ │ ├── activity_main_drawer.xml │ │ ├── main.xml │ │ ├── menu_train_file_list.xml │ │ ├── toolbar_files.xml │ │ ├── toolbar_filter.xml │ │ ├── toolbar_train.xml │ │ └── toolbar_workout_list.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── drawables.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── favorite_button.xml │ │ ├── filepaths.xml │ │ ├── pref_data_sync.xml │ │ ├── pref_general.xml │ │ ├── pref_headers.xml │ │ └── pref_notification.xml │ └── test │ └── java │ └── com │ └── trainerdb │ └── trainerandroid │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | TrainerAndroid -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 49 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TrainerAndroid 2 | TrainerAndroid is an Android App for power-based cycling training, deeply based on GoldenCheetah project. 3 | * Workouts: http://trainerdb-84bdb.firebaseapp.com 4 | * Wiki and screenshots: http://github.com/TrainerDb/TrainerAndroid/wiki 5 | 6 | ## Current Features 7 | * Complete power-based workouts. 8 | * Realtime workout data (ex. Watts, Cadence, Speed, Normalized Power, ...) 9 | * Connect to ANT+ devices (Speed, Cadence, Hr and Power devices) 10 | * Virtual power curves from GoldenCheetah software. 11 | * Share workouts completed. (CSV file) 12 | 13 | ## Future 14 | * Connect to Bluetooth Smart devices 15 | * Open and analyze completed workouts. 16 | * Cloud-based database, web sync. 17 | * Much more... 18 | 19 | ## Compatible Devices 20 | * ANT+ enabled Android devices. Many Android mobile devices support ANT+ natively. 21 | For details and ANT plugins visit: http://www.thisisant.com/consumer/ant-101/ant-in-phones 22 | 23 | ## Licensing 24 | * Is released under an Open Source license. 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.trainerdb.trainerandroid" 9 | minSdkVersion 16 10 | targetSdkVersion 24 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile files('libs/antpluginlib_3-5-0.jar') 26 | compile 'com.android.support:appcompat-v7:24.2.1' 27 | compile 'com.android.support:support-v4:24.2.1' 28 | compile 'com.android.support:recyclerview-v7:24.2.1' 29 | compile 'com.android.support:design:24.2.1' 30 | compile 'com.google.firebase:firebase-core:9.4.0' 31 | compile 'com.google.firebase:firebase-database:9.4.0' 32 | compile 'com.opencsv:opencsv:3.8' 33 | } 34 | apply plugin: 'com.google.gms.google-services' 35 | -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "632821488129", 4 | "firebase_url": "https://trainerdb-84bdb.firebaseio.com", 5 | "project_id": "trainerdb-84bdb", 6 | "storage_bucket": "trainerdb-84bdb.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:632821488129:android:328a37a451a02ed3", 12 | "android_client_info": { 13 | "package_name": "com.trainerdb.trainerandroid" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "632821488129-m545e13kj7oqk3jj660cu7qknokf13p4.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyCWdR8nYgA5GiadOB609qAjY7zdVQ0TKOk" 25 | } 26 | ], 27 | "services": { 28 | "analytics_service": { 29 | "status": 1 30 | }, 31 | "appinvite_service": { 32 | "status": 1, 33 | "other_platform_oauth_client": [] 34 | }, 35 | "ads_service": { 36 | "status": 2 37 | } 38 | } 39 | } 40 | ], 41 | "configuration_version": "1" 42 | } -------------------------------------------------------------------------------- /app/libs/antpluginlib_3-5-0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/libs/antpluginlib_3-5-0.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\dcotrim\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/trainerdb/trainerandroid/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 17 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 42 | 45 | 46 | 49 | 54 | 57 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/AutoResizeTextView.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.res.Resources; 6 | import android.graphics.RectF; 7 | import android.os.Build; 8 | import android.text.Layout.Alignment; 9 | import android.text.StaticLayout; 10 | import android.text.TextPaint; 11 | import android.util.AttributeSet; 12 | import android.util.SparseIntArray; 13 | import android.util.TypedValue; 14 | import android.widget.TextView; 15 | 16 | public class AutoResizeTextView extends TextView { 17 | private interface SizeTester { 18 | /** 19 | * 20 | * @param suggestedSize 21 | * Size of text to be tested 22 | * @param availableSpace 23 | * available space in which text must fit 24 | * @return an integer < 0 if after applying {@code suggestedSize} to 25 | * text, it takes less space than {@code availableSpace}, > 0 26 | * otherwise 27 | */ 28 | public int onTestSize(int suggestedSize, RectF availableSpace); 29 | } 30 | 31 | private RectF mTextRect = new RectF(); 32 | 33 | private RectF mAvailableSpaceRect; 34 | 35 | private SparseIntArray mTextCachedSizes; 36 | 37 | private TextPaint mPaint; 38 | 39 | private float mMaxTextSize; 40 | 41 | private float mSpacingMult = 1.0f; 42 | 43 | private float mSpacingAdd = 0.0f; 44 | 45 | private float mMinTextSize = 20; 46 | 47 | private int mWidthLimit; 48 | 49 | private static final int NO_LINE_LIMIT = -1; 50 | private int mMaxLines; 51 | 52 | private boolean mEnableSizeCache = true; 53 | private boolean mInitiallized; 54 | 55 | public AutoResizeTextView(Context context) { 56 | super(context); 57 | initialize(); 58 | } 59 | 60 | public AutoResizeTextView(Context context, AttributeSet attrs) { 61 | super(context, attrs); 62 | initialize(); 63 | } 64 | 65 | public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) { 66 | super(context, attrs, defStyle); 67 | initialize(); 68 | } 69 | 70 | private void initialize() { 71 | mPaint = new TextPaint(getPaint()); 72 | mMaxTextSize = getTextSize(); 73 | mAvailableSpaceRect = new RectF(); 74 | mTextCachedSizes = new SparseIntArray(); 75 | if (mMaxLines == 0) { 76 | // no value was assigned during construction 77 | mMaxLines = NO_LINE_LIMIT; 78 | } 79 | mInitiallized = true; 80 | } 81 | 82 | @Override 83 | public void setText(final CharSequence text, BufferType type) { 84 | super.setText(text, type); 85 | adjustTextSize(text.toString()); 86 | } 87 | 88 | @Override 89 | public void setTextSize(float size) { 90 | mMaxTextSize = size; 91 | mTextCachedSizes.clear(); 92 | adjustTextSize(getText().toString()); 93 | } 94 | 95 | @Override 96 | public void setMaxLines(int maxlines) { 97 | super.setMaxLines(maxlines); 98 | mMaxLines = maxlines; 99 | reAdjust(); 100 | } 101 | 102 | public int getMaxLines() { 103 | return mMaxLines; 104 | } 105 | 106 | @Override 107 | public void setSingleLine() { 108 | super.setSingleLine(); 109 | mMaxLines = 1; 110 | reAdjust(); 111 | } 112 | 113 | @Override 114 | public void setSingleLine(boolean singleLine) { 115 | super.setSingleLine(singleLine); 116 | if (singleLine) { 117 | mMaxLines = 1; 118 | } else { 119 | mMaxLines = NO_LINE_LIMIT; 120 | } 121 | reAdjust(); 122 | } 123 | 124 | @Override 125 | public void setLines(int lines) { 126 | super.setLines(lines); 127 | mMaxLines = lines; 128 | reAdjust(); 129 | } 130 | 131 | @Override 132 | public void setTextSize(int unit, float size) { 133 | Context c = getContext(); 134 | Resources r; 135 | 136 | if (c == null) 137 | r = Resources.getSystem(); 138 | else 139 | r = c.getResources(); 140 | mMaxTextSize = TypedValue.applyDimension(unit, size, 141 | r.getDisplayMetrics()); 142 | mTextCachedSizes.clear(); 143 | adjustTextSize(getText().toString()); 144 | } 145 | 146 | @Override 147 | public void setLineSpacing(float add, float mult) { 148 | super.setLineSpacing(add, mult); 149 | mSpacingMult = mult; 150 | mSpacingAdd = add; 151 | } 152 | 153 | /** 154 | * Set the lower text size limit and invalidate the view 155 | * 156 | * @param minTextSize 157 | */ 158 | public void setMinTextSize(float minTextSize) { 159 | mMinTextSize = minTextSize; 160 | reAdjust(); 161 | } 162 | 163 | private void reAdjust() { 164 | adjustTextSize(getText().toString()); 165 | } 166 | 167 | private void adjustTextSize(String string) { 168 | if (!mInitiallized) { 169 | return; 170 | } 171 | int startSize = (int) mMinTextSize; 172 | int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom() 173 | - getCompoundPaddingTop(); 174 | mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft() 175 | - getCompoundPaddingRight(); 176 | mAvailableSpaceRect.right = mWidthLimit; 177 | mAvailableSpaceRect.bottom = heightLimit; 178 | super.setTextSize( 179 | TypedValue.COMPLEX_UNIT_PX, 180 | efficientTextSizeSearch(startSize, (int) mMaxTextSize, 181 | mSizeTester, mAvailableSpaceRect)); 182 | } 183 | 184 | private final SizeTester mSizeTester = new SizeTester() { 185 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 186 | @Override 187 | public int onTestSize(int suggestedSize, RectF availableSPace) { 188 | mPaint.setTextSize(suggestedSize); 189 | String text = getText().toString(); 190 | boolean singleline = getMaxLines() == 1; 191 | if (singleline) { 192 | mTextRect.bottom = mPaint.getFontSpacing(); 193 | mTextRect.right = mPaint.measureText(text); 194 | } else { 195 | StaticLayout layout = new StaticLayout(text, mPaint, 196 | mWidthLimit, Alignment.ALIGN_NORMAL, mSpacingMult, 197 | mSpacingAdd, true); 198 | // return early if we have more lines 199 | if (getMaxLines() != NO_LINE_LIMIT 200 | && layout.getLineCount() > getMaxLines()) { 201 | return 1; 202 | } 203 | mTextRect.bottom = layout.getHeight(); 204 | int maxWidth = -1; 205 | for (int i = 0; i < layout.getLineCount(); i++) { 206 | if (maxWidth < layout.getLineWidth(i)) { 207 | maxWidth = (int) layout.getLineWidth(i); 208 | } 209 | } 210 | mTextRect.right = maxWidth; 211 | } 212 | 213 | mTextRect.offsetTo(0, 0); 214 | if (availableSPace.contains(mTextRect)) { 215 | // may be too small, don't worry we will find the best match 216 | return -1; 217 | } else { 218 | // too big 219 | return 1; 220 | } 221 | } 222 | }; 223 | 224 | /** 225 | * Enables or disables size caching, enabling it will improve performance 226 | * where you are animating a value inside TextView. This stores the font 227 | * size against getText().length() Be careful though while enabling it as 0 228 | * takes more space than 1 on some fonts and so on. 229 | * 230 | * @param enable 231 | * enable font size caching 232 | */ 233 | public void enableSizeCache(boolean enable) { 234 | mEnableSizeCache = enable; 235 | mTextCachedSizes.clear(); 236 | adjustTextSize(getText().toString()); 237 | } 238 | 239 | private int efficientTextSizeSearch(int start, int end, 240 | SizeTester sizeTester, RectF availableSpace) { 241 | if (!mEnableSizeCache) { 242 | return binarySearch(start, end, sizeTester, availableSpace); 243 | } 244 | String text = getText().toString(); 245 | int key = text == null ? 0 : text.length(); 246 | int size = mTextCachedSizes.get(key); 247 | if (size != 0) { 248 | return size; 249 | } 250 | size = binarySearch(start, end, sizeTester, availableSpace); 251 | mTextCachedSizes.put(key, size); 252 | return size; 253 | } 254 | 255 | private static int binarySearch(int start, int end, SizeTester sizeTester, 256 | RectF availableSpace) { 257 | int lastBest = start; 258 | int lo = start; 259 | int hi = end - 1; 260 | int mid = 0; 261 | while (lo <= hi) { 262 | mid = (lo + hi) >>> 1; 263 | int midValCmp = sizeTester.onTestSize(mid, availableSpace); 264 | if (midValCmp < 0) { 265 | lastBest = lo; 266 | lo = mid + 1; 267 | } else if (midValCmp > 0) { 268 | hi = mid - 1; 269 | lastBest = hi; 270 | } else { 271 | return mid; 272 | } 273 | } 274 | // make sure to return last best 275 | // this is what should always be returned 276 | return lastBest; 277 | 278 | } 279 | 280 | @Override 281 | protected void onTextChanged(final CharSequence text, final int start, 282 | final int before, final int after) { 283 | super.onTextChanged(text, start, before, after); 284 | reAdjust(); 285 | } 286 | 287 | @Override 288 | protected void onSizeChanged(int width, int height, int oldwidth, 289 | int oldheight) { 290 | mTextCachedSizes.clear(); 291 | super.onSizeChanged(width, height, oldwidth, oldheight); 292 | if (width != oldwidth || height != oldheight) { 293 | reAdjust(); 294 | } 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/Compression.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid; 2 | 3 | import android.util.Base64; 4 | 5 | import java.io.ByteArrayInputStream; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.InputStreamReader; 8 | import java.io.Reader; 9 | import java.util.zip.Deflater; 10 | import java.util.zip.DeflaterOutputStream; 11 | import java.util.zip.InflaterInputStream; 12 | 13 | /** 14 | * Created by dcotrim on 01/08/2016. 15 | */ 16 | public class Compression { 17 | public static String compress(String s) { 18 | DeflaterOutputStream def = null; 19 | String compressed = null; 20 | try { 21 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 22 | // create deflater without header 23 | def = new DeflaterOutputStream(out, new Deflater(Deflater.DEFAULT_COMPRESSION, false)); 24 | def.write(s.getBytes()); 25 | def.close(); 26 | compressed = Base64.encodeToString(out.toByteArray(), Base64.DEFAULT); 27 | System.out.println(compressed); 28 | } catch (Exception e) { 29 | throw new RuntimeException(e); 30 | } 31 | return compressed; 32 | } 33 | 34 | public static String decompress(String s) { 35 | InflaterInputStream inf = null; 36 | StringBuilder out = new StringBuilder(); 37 | try { 38 | inf = new InflaterInputStream(new ByteArrayInputStream(Base64.decode(s, Base64.DEFAULT))); 39 | 40 | final int bufferSize = 1024; 41 | final char[] buffer = new char[bufferSize]; 42 | 43 | Reader in = new InputStreamReader(inf); 44 | 45 | for (; ; ) { 46 | int rsz = in.read(buffer, 0, buffer.length); 47 | if (rsz < 0) 48 | break; 49 | out.append(buffer, 0, rsz); 50 | } 51 | } catch (Exception e) { 52 | throw new RuntimeException(e); 53 | } 54 | return out.toString(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/IGetAsyncListener.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid; 2 | 3 | /** 4 | * Created by dcotrim on 20/09/2016. 5 | */ 6 | public interface IGetAsyncListener { 7 | void onDataGet(boolean success, T data); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/TrainApplication.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.preference.PreferenceManager; 7 | 8 | import com.trainerdb.trainerandroid.data.PowerZone; 9 | import com.trainerdb.trainerandroid.train.DeviceConfiguration; 10 | import com.trainerdb.trainerandroid.train.TrainController; 11 | 12 | import java.util.ArrayList; 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | /** 18 | * Created by dcotrim on 21/09/2016. 19 | */ 20 | public class TrainApplication extends Application { 21 | private static Context context; 22 | private static TrainController trainController; 23 | private static SharedPreferences preferences; 24 | 25 | public static String formatTime(long milliSeconds) { 26 | long second = (milliSeconds / 1000) % 60; 27 | long minute = (milliSeconds / (1000 * 60)) % 60; 28 | long hour = (milliSeconds / (1000 * 60 * 60)) % 24; 29 | if (hour > 0) 30 | return String.format("%d:%02d:%02d", hour, minute, second); 31 | else 32 | return String.format("%02d:%02d", minute, second); 33 | 34 | } 35 | 36 | public void onCreate() { 37 | super.onCreate(); 38 | context = getApplicationContext(); 39 | preferences = PreferenceManager.getDefaultSharedPreferences(context); 40 | } 41 | 42 | public static int getFTP() { 43 | return Integer.parseInt(preferences.getString("ftp", "200")); 44 | } 45 | 46 | public static Context getAppContext() { 47 | return TrainApplication.context; 48 | } 49 | 50 | public static int getVirtualPowerId() { 51 | return Integer.parseInt(preferences.getString("virtual", "0")); 52 | } 53 | public static DeviceConfiguration.DeviceType getDeviceType() { 54 | return DeviceConfiguration.DeviceType.valueOf(preferences.getString("device", "DEV_ANTLOCAL")); 55 | } 56 | public static void setTrainController(TrainController newTrainController) { 57 | if (trainController != null) { 58 | trainController.stop(); 59 | trainController.disconnect(); 60 | } 61 | trainController = newTrainController; 62 | } 63 | 64 | public static TrainController getTrainController() { 65 | return trainController; 66 | } 67 | 68 | public static boolean isFavorite(String key) { 69 | Set values = preferences.getStringSet("workout_favorite", null); 70 | if (values != null && values.contains(key)) 71 | return true; 72 | return false; 73 | } 74 | 75 | public static void addFavorite(String key) { 76 | Set def = new HashSet<>(); 77 | Set values = new HashSet<>(preferences.getStringSet("workout_favorite", def)); 78 | if (!values.contains(key)) { 79 | values.add(key); 80 | preferences.edit().putStringSet("workout_favorite", values).apply(); 81 | } 82 | } 83 | 84 | public static void removeFavorite(String key) { 85 | Set def = new HashSet<>(); 86 | Set values = new HashSet<>(preferences.getStringSet("workout_favorite", def)); 87 | if (values.contains(key)) { 88 | values.remove(key); 89 | preferences.edit().putStringSet("workout_favorite", values).apply(); 90 | } 91 | } 92 | 93 | private static double calcPerimeter(double rim, double tire) { 94 | if (rim > 0 && tire > 0) { 95 | return Math.round((rim + tire) * Math.PI); 96 | } 97 | return 0; 98 | } 99 | 100 | public static double getWheelCircunference() { 101 | double rim = Double.parseDouble(preferences.getString("rim", "622")); 102 | double tire = Double.parseDouble(preferences.getString("tire", "46")); 103 | return calcPerimeter(rim, tire); 104 | } 105 | 106 | public static List getPowerZones() { 107 | int ftp = getFTP(); 108 | List powerZones = new ArrayList<>(); 109 | powerZones.add(0, new PowerZone("Active Recovery", 0, (int) (0.55 * ftp))); 110 | powerZones.add(1, new PowerZone("Endurance", (int) (0.55 * ftp), (int) (0.75 * ftp))); 111 | powerZones.add(2, new PowerZone("Tempo", (int) (0.75 * ftp), (int) (0.90 * ftp))); 112 | powerZones.add(3, new PowerZone("Lactate Threshold", (int) (0.90 * ftp), (int) (1.05 * ftp))); 113 | powerZones.add(4, new PowerZone("VO2 Max", (int) (1.05 * ftp), (int) (1.2 * ftp))); 114 | powerZones.add(5, new PowerZone("Anaerobic Capacity", (int) (1.2 * ftp), (int) (1.5 * ftp))); 115 | powerZones.add(6, new PowerZone("Neuromuscular Power", (int) (1.5 * ftp), Integer.MAX_VALUE)); 116 | return powerZones; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/TrainWorkoutView.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.PointF; 9 | import android.graphics.Rect; 10 | import android.graphics.RectF; 11 | import android.util.AttributeSet; 12 | 13 | import com.trainerdb.trainerandroid.train.TrainState; 14 | 15 | /** 16 | * Created by dcotrim on 23/06/2016. 17 | */ 18 | public class TrainWorkoutView extends WorkoutView { 19 | private TrainState state; 20 | Paint pPower = new Paint(); 21 | Paint pHr = new Paint(); 22 | Paint pCadence = new Paint(); 23 | Paint defaultPaint = new Paint(); 24 | Paint pText = new Paint(); 25 | Paint pSelectInterval = new Paint(); 26 | Bitmap cachedBitmap; 27 | PointF lastPointWatts, lastPointHr, lastPointCadence; 28 | Rect srcZoom; 29 | int selectedInterval = 0; 30 | 31 | public enum Transform { 32 | WATTS, HR, CADENCE, SPEED 33 | } 34 | 35 | public TrainWorkoutView(Context context, AttributeSet attrs) { 36 | super(context, attrs); 37 | pCadence.setColor(Color.WHITE); 38 | pCadence.setStrokeWidth(5); 39 | pPower.setColor(Color.YELLOW); 40 | pPower.setStrokeWidth(5); 41 | pHr.setColor(Color.RED); 42 | pHr.setStrokeWidth(5); 43 | pText.setColor(Color.WHITE); 44 | pText.setTextAlign(Paint.Align.CENTER); 45 | pSelectInterval.setColor(Color.argb(64, 255, 255, 0)); 46 | pSelectInterval.setStyle(Paint.Style.FILL); 47 | 48 | IHEIGHT = 10; 49 | THEIGHT = 35; 50 | BHEIGHT = 45; 51 | LWIDTH = 75; 52 | RWIDTH = 35; 53 | POWERSCALEWIDTH = 5; 54 | } 55 | 56 | public void setState(TrainState state) { 57 | this.state = state; 58 | this.workout = state.workout; 59 | this.ergs = workout.getErg().getCoursePoints(); 60 | recompute(); 61 | postInvalidate(); 62 | } 63 | 64 | public Bitmap getCachedBitmap() { 65 | return cachedBitmap; 66 | } 67 | 68 | protected PointF transform(float seconds, float watts, Transform type) { 69 | RectF c = canvas(); 70 | 71 | float xratio = c.width() / (maxVx - minVx); 72 | float yratio = c.height() / (maxY - minY); 73 | 74 | return new PointF(c.left - (minVx * xratio) + (seconds * xratio), c.bottom - (watts * yratio)); 75 | } 76 | 77 | private void paintTelemetry(boolean firstDraw, Canvas canvas) { 78 | if (!firstDraw && !state.recording) return; 79 | 80 | long now = state.now; 81 | 82 | PointF here; 83 | if (state.watts.size() > 0) { 84 | int pos = state.watts.size() - 1; 85 | if (lastPointWatts == null) { 86 | lastPointWatts = transform(0, state.watts.get(0), Transform.WATTS); 87 | for (int i = 1; i <= pos; i++) { 88 | here = transform(i * 1000, state.watts.get(i), Transform.WATTS); 89 | canvas.drawLine(lastPointWatts.x, lastPointWatts.y, here.x, here.y, pPower); 90 | lastPointWatts = here; 91 | } 92 | } else { 93 | here = transform(now, state.watts.get(pos), Transform.WATTS); 94 | canvas.drawLine(lastPointWatts.x, lastPointWatts.y, here.x, here.y, pPower); 95 | lastPointWatts = here; 96 | } 97 | } 98 | 99 | if (state.cadence.size() > 1) { 100 | int pos = state.cadence.size() - 1; 101 | if (lastPointCadence == null) { 102 | lastPointCadence = transform(0, state.cadence.get(0), Transform.CADENCE); 103 | for (int i = 1; i <= pos; i++) { 104 | here = transform(i * 1000, state.cadence.get(i), Transform.CADENCE); 105 | canvas.drawLine(lastPointCadence.x, lastPointCadence.y, here.x, here.y, pCadence); 106 | lastPointCadence = here; 107 | } 108 | } else { 109 | here = transform(now, state.cadence.get(pos), Transform.CADENCE); 110 | canvas.drawLine(lastPointCadence.x, lastPointCadence.y, here.x, here.y, pCadence); 111 | lastPointCadence = here; 112 | } 113 | } 114 | 115 | if (state.hr.size() > 1) { 116 | int pos = state.hr.size() - 1; 117 | if (lastPointHr == null) { 118 | lastPointHr = transform(0, state.hr.get(0), Transform.HR); 119 | for (int i = 1; i <= pos; i++) { 120 | here = transform(i * 1000, state.hr.get(i), Transform.HR); 121 | canvas.drawLine(lastPointHr.x, lastPointHr.y, here.x, here.y, pHr); 122 | lastPointHr = here; 123 | } 124 | } else { 125 | here = transform(now, state.hr.get(pos), Transform.HR); 126 | canvas.drawLine(lastPointHr.x, lastPointHr.y, here.x, here.y, pHr); 127 | lastPointHr = here; 128 | } 129 | } 130 | 131 | } 132 | 133 | private void paintNow(Canvas canvas) { 134 | if (!state.recording) return; 135 | 136 | // get now 137 | float px = transform(state.now, 0).x; 138 | 139 | Paint linePen = new Paint(); 140 | linePen.setColor(Color.YELLOW); 141 | linePen.setStrokeWidth(5); 142 | 143 | canvas.drawLine(px, canvas().top, px, canvas().bottom, linePen); 144 | } 145 | 146 | private void selectInterval(Canvas canvas) { 147 | if (selectedInterval != 0) { 148 | 149 | } 150 | } 151 | 152 | private Rect calcIntervalCanvas() { 153 | Rect rectInterval = new Rect(); 154 | long timeLeft = state.now - (60 * 1000); 155 | long timeRight = state.now + (2 * 60 * 1000); 156 | 157 | float left = transform(timeLeft, 0).x; 158 | float right = transform(timeRight, 0).x; 159 | 160 | (new RectF(left, canvas().top, right, canvas().bottom)).round(rectInterval); 161 | return rectInterval; 162 | } 163 | 164 | private void doInitialDrawing(Canvas canvas) { 165 | } 166 | 167 | public void setSelectedInterval(int selectedInterval) { 168 | this.selectedInterval = selectedInterval; 169 | this.postInvalidate(); 170 | } 171 | 172 | @Override 173 | protected void onDraw(Canvas canvas) { 174 | if (state == null) { 175 | super.onDraw(canvas); 176 | return; 177 | } 178 | 179 | //Rect srcFull = new Rect(0, 0, getWidth(), getHeight()); 180 | //RectF rectInterval = new RectF(0, 0, getWidth(), getHeight() / 3); 181 | //RectF rectFull = new RectF(0, getHeight() / 3, getWidth(), getHeight()); 182 | 183 | if (!initialDrawingIsPerformed) { 184 | lastPointWatts = lastPointCadence = lastPointHr = null; 185 | super.onDraw(canvas); 186 | this.cachedBitmap = Bitmap.createBitmap(getWidth(), getHeight(), 187 | Bitmap.Config.ARGB_8888); //Change to lower bitmap config if possible. 188 | Canvas cacheCanvas = new Canvas(this.cachedBitmap); 189 | super.onDraw(cacheCanvas); 190 | doInitialDrawing(cacheCanvas); 191 | paintTelemetry(true, cacheCanvas); 192 | canvas.drawBitmap(this.cachedBitmap, 0, 0, defaultPaint); 193 | //canvas.drawBitmap(this.cachedBitmap, srcFull, rectInterval, defaultPaint); 194 | //canvas.drawBitmap(this.cachedBitmap, srcFull, rectFull, defaultPaint); 195 | initialDrawingIsPerformed = true; 196 | } else { 197 | //calcIntervalCanvas(); 198 | Canvas cacheCanvas = new Canvas(this.cachedBitmap); 199 | paintTelemetry(false, cacheCanvas); 200 | canvas.drawBitmap(this.cachedBitmap, 0, 0, defaultPaint); 201 | //canvas.drawBitmap(cachedBitmap, srcFull, rectFull, defaultPaint); 202 | //canvas.drawBitmap(cachedBitmap, calcIntervalCanvas(), rectInterval, defaultPaint); 203 | paintNow(canvas); 204 | selectInterval(canvas); 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/activities/AppCompatPreferenceActivity.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.activities; 2 | 3 | import android.content.res.Configuration; 4 | import android.os.Bundle; 5 | import android.preference.PreferenceActivity; 6 | import android.support.annotation.LayoutRes; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.app.ActionBar; 9 | import android.support.v7.app.AppCompatDelegate; 10 | import android.support.v7.widget.Toolbar; 11 | import android.view.MenuInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | 15 | /** 16 | * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls 17 | * to be used with AppCompat. 18 | */ 19 | public abstract class AppCompatPreferenceActivity extends PreferenceActivity { 20 | 21 | private AppCompatDelegate mDelegate; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | getDelegate().installViewFactory(); 26 | getDelegate().onCreate(savedInstanceState); 27 | super.onCreate(savedInstanceState); 28 | } 29 | 30 | @Override 31 | protected void onPostCreate(Bundle savedInstanceState) { 32 | super.onPostCreate(savedInstanceState); 33 | getDelegate().onPostCreate(savedInstanceState); 34 | } 35 | 36 | public ActionBar getSupportActionBar() { 37 | return getDelegate().getSupportActionBar(); 38 | } 39 | 40 | public void setSupportActionBar(@Nullable Toolbar toolbar) { 41 | getDelegate().setSupportActionBar(toolbar); 42 | } 43 | 44 | @Override 45 | public MenuInflater getMenuInflater() { 46 | return getDelegate().getMenuInflater(); 47 | } 48 | 49 | @Override 50 | public void setContentView(@LayoutRes int layoutResID) { 51 | getDelegate().setContentView(layoutResID); 52 | } 53 | 54 | @Override 55 | public void setContentView(View view) { 56 | getDelegate().setContentView(view); 57 | } 58 | 59 | @Override 60 | public void setContentView(View view, ViewGroup.LayoutParams params) { 61 | getDelegate().setContentView(view, params); 62 | } 63 | 64 | @Override 65 | public void addContentView(View view, ViewGroup.LayoutParams params) { 66 | getDelegate().addContentView(view, params); 67 | } 68 | 69 | @Override 70 | protected void onPostResume() { 71 | super.onPostResume(); 72 | getDelegate().onPostResume(); 73 | } 74 | 75 | @Override 76 | protected void onTitleChanged(CharSequence title, int color) { 77 | super.onTitleChanged(title, color); 78 | getDelegate().setTitle(title); 79 | } 80 | 81 | @Override 82 | public void onConfigurationChanged(Configuration newConfig) { 83 | super.onConfigurationChanged(newConfig); 84 | getDelegate().onConfigurationChanged(newConfig); 85 | } 86 | 87 | @Override 88 | protected void onStop() { 89 | super.onStop(); 90 | getDelegate().onStop(); 91 | } 92 | 93 | @Override 94 | protected void onDestroy() { 95 | super.onDestroy(); 96 | getDelegate().onDestroy(); 97 | } 98 | 99 | public void invalidateOptionsMenu() { 100 | getDelegate().invalidateOptionsMenu(); 101 | } 102 | 103 | private AppCompatDelegate getDelegate() { 104 | if (mDelegate == null) { 105 | mDelegate = AppCompatDelegate.create(this, null); 106 | } 107 | return mDelegate; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/activities/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.activities; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.design.widget.FloatingActionButton; 6 | import android.support.design.widget.Snackbar; 7 | import android.view.View; 8 | import android.support.design.widget.NavigationView; 9 | import android.support.v4.view.GravityCompat; 10 | import android.support.v4.widget.DrawerLayout; 11 | import android.support.v7.app.ActionBarDrawerToggle; 12 | import android.support.v7.app.AppCompatActivity; 13 | import android.support.v7.widget.Toolbar; 14 | import android.view.Menu; 15 | import android.view.MenuItem; 16 | 17 | import com.trainerdb.trainerandroid.R; 18 | 19 | public class MainActivity extends AppCompatActivity 20 | implements NavigationView.OnNavigationItemSelectedListener { 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_main); 26 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 27 | setSupportActionBar(toolbar); 28 | 29 | DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); 30 | ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( 31 | this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); 32 | drawer.setDrawerListener(toggle); 33 | toggle.syncState(); 34 | 35 | NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); 36 | navigationView.setNavigationItemSelectedListener(this); 37 | } 38 | 39 | @Override 40 | public void onBackPressed() { 41 | DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); 42 | if (drawer.isDrawerOpen(GravityCompat.START)) { 43 | drawer.closeDrawer(GravityCompat.START); 44 | } else { 45 | super.onBackPressed(); 46 | } 47 | } 48 | 49 | @Override 50 | public boolean onCreateOptionsMenu(Menu menu) { 51 | // Inflate the menu; this adds items to the action bar if it is present. 52 | getMenuInflater().inflate(R.menu.main, menu); 53 | return true; 54 | } 55 | 56 | @Override 57 | public boolean onOptionsItemSelected(MenuItem item) { 58 | // Handle action bar item clicks here. The action bar will 59 | // automatically handle clicks on the Home/Up button, so long 60 | // as you specify a parent activity in AndroidManifest.xml. 61 | int id = item.getItemId(); 62 | 63 | //noinspection SimplifiableIfStatement 64 | if (id == R.id.action_settings) { 65 | Intent intent = new Intent(MainActivity.this, SettingsActivity.class); 66 | startActivity(intent); 67 | return true; 68 | } 69 | 70 | return super.onOptionsItemSelected(item); 71 | } 72 | 73 | @SuppressWarnings("StatementWithEmptyBody") 74 | @Override 75 | public boolean onNavigationItemSelected(MenuItem item) { 76 | // Handle navigation view item clicks here. 77 | int id = item.getItemId(); 78 | 79 | if (id == R.id.nav_workouts) { 80 | Intent intent = new Intent(this, WorkoutListActivity.class); 81 | this.startActivity(intent); 82 | } else if (id == R.id.nav_train_files) { 83 | Intent intent = new Intent(this, TrainFileListActivity.class); 84 | this.startActivity(intent); 85 | } else if (id == R.id.nav_send) { 86 | 87 | } 88 | 89 | DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); 90 | drawer.closeDrawer(GravityCompat.START); 91 | return true; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/activities/TrainFileListActivity.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.activities; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.content.Intent; 7 | import android.net.Uri; 8 | import android.os.Bundle; 9 | import android.support.design.widget.FloatingActionButton; 10 | import android.support.design.widget.Snackbar; 11 | import android.support.v4.content.FileProvider; 12 | import android.support.v7.app.ActionBar; 13 | import android.support.v7.app.AlertDialog; 14 | import android.support.v7.app.AppCompatActivity; 15 | import android.support.v7.widget.Toolbar; 16 | import android.view.LayoutInflater; 17 | import android.view.Menu; 18 | import android.view.MenuItem; 19 | import android.view.View; 20 | import android.view.ViewGroup; 21 | import android.widget.AdapterView; 22 | import android.widget.ArrayAdapter; 23 | import android.widget.CheckBox; 24 | import android.widget.ListView; 25 | import android.widget.TextView; 26 | 27 | import com.trainerdb.trainerandroid.R; 28 | import com.trainerdb.trainerandroid.train.TrainFile; 29 | 30 | import java.io.File; 31 | import java.util.ArrayList; 32 | import java.util.Iterator; 33 | import java.util.List; 34 | 35 | public class TrainFileListActivity extends AppCompatActivity { 36 | 37 | List files; 38 | ListView list; 39 | FileListAdapter adapter; 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setContentView(R.layout.activity_train_file_list); 45 | 46 | Toolbar myToolbar = (Toolbar) findViewById(R.id.toolbar); 47 | setSupportActionBar(myToolbar); 48 | this.getSupportActionBar().setTitle("Files"); 49 | 50 | // Show the Up button in the action bar. 51 | ActionBar actionBar = getSupportActionBar(); 52 | if (actionBar != null) { 53 | actionBar.setDisplayHomeAsUpEnabled(true); 54 | } 55 | 56 | files = getListFiles(this.getFilesDir()); 57 | files.addAll(getListFiles(this.getExternalFilesDir(null))); 58 | 59 | list = (ListView) findViewById(R.id.listFiles); 60 | 61 | adapter = new FileListAdapter(this, files); 62 | list.setAdapter(adapter); 63 | 64 | list.setOnItemClickListener(new AdapterView.OnItemClickListener() { 65 | @Override 66 | public void onItemClick(AdapterView parent, View view, int position, long id) { 67 | CheckBox chkSelected = (CheckBox) view.findViewById(R.id.checkBoxSelected); 68 | if (chkSelected.isChecked()) 69 | files.get(position).setSelected(false); 70 | else 71 | files.get(position).setSelected(true); 72 | 73 | adapter.notifyDataSetChanged(); 74 | } 75 | }); 76 | } 77 | 78 | @Override 79 | public boolean onOptionsItemSelected(MenuItem item) { 80 | int id = item.getItemId(); 81 | if (id == R.id.action_delete) { 82 | AlertDialog.Builder abuilder = new AlertDialog.Builder(this); 83 | abuilder.setMessage("Delete workout(s)?"); 84 | abuilder.setPositiveButton("Yes", new DialogInterface.OnClickListener() { 85 | public void onClick(DialogInterface dialog, int which) { 86 | Iterator i = files.iterator(); 87 | while (i.hasNext()) { 88 | TrainFile file = i.next(); 89 | if (file.isSelected()) { 90 | file.getFile().delete(); 91 | i.remove(); 92 | } 93 | } 94 | adapter.notifyDataSetChanged(); 95 | } 96 | }); 97 | abuilder.setNegativeButton("No", new DialogInterface.OnClickListener() { 98 | public void onClick(DialogInterface dialog, int which) { 99 | } 100 | }); 101 | AlertDialog alert = abuilder.create(); 102 | alert.show(); 103 | } else if (id == R.id.action_share) { 104 | shareFilesSelected(); 105 | /* 106 | Iterator i = files.iterator(); 107 | while (i.hasNext()) { 108 | TrainFile file = i.next(); 109 | if (file.isSelected()) { 110 | 111 | final FirebaseDatabase database = FirebaseDatabase.getInstance(); 112 | final DatabaseReference ref = database.getReference("workouts"); 113 | ref.child("145788").addListenerForSingleValueEvent(new ValueEventListener() { 114 | @Override 115 | public void onDataChange(DataSnapshot dataSnapshot) { 116 | final Workout w = dataSnapshot.getValue(Workout.class); 117 | w.downloadData(new CompleteListener() { 118 | @Override 119 | public void onComplete(boolean success) { 120 | FileController controller = new FileController(w, files.get(0), 1); 121 | controller.exportRecord(); 122 | } 123 | }); 124 | } 125 | 126 | @Override 127 | public void onCancelled(DatabaseError databaseError) { 128 | } 129 | }); 130 | } 131 | } 132 | */ 133 | } 134 | 135 | return super.onOptionsItemSelected(item); 136 | } 137 | 138 | @Override 139 | public boolean onCreateOptionsMenu(Menu menu) { 140 | getMenuInflater().inflate(R.menu.toolbar_files, menu); 141 | return true; 142 | } 143 | 144 | private void shareFilesSelected() { 145 | Intent shareIntent = new Intent(); 146 | shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE); 147 | shareIntent.setType("application/xml"); 148 | 149 | ArrayList filesUri = new ArrayList(); 150 | for (TrainFile file : files) { 151 | if (file.isSelected()) { 152 | filesUri.add(FileProvider.getUriForFile(this, "com.trainerandroid.fileprovider", file.getFile())); 153 | } 154 | } 155 | 156 | shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 157 | //shareIntent.setDataAndType(fileUri, getContentResolver().getType(fileUri)); 158 | // Set the result 159 | this.setResult(Activity.RESULT_OK, shareIntent); 160 | 161 | shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, filesUri); 162 | startActivity(Intent.createChooser(shareIntent, "Share")); 163 | } 164 | 165 | private List getListFiles(File parentDir) { 166 | ArrayList inFiles = new ArrayList<>(); 167 | File[] files = parentDir.listFiles(); 168 | for (File file : files) { 169 | if (file.isDirectory()) { 170 | inFiles.addAll(getListFiles(file)); 171 | } else { 172 | if (file.getName().endsWith(".csv")) { 173 | inFiles.add(new TrainFile(file)); 174 | } 175 | } 176 | } 177 | return inFiles; 178 | } 179 | 180 | public class FileListAdapter extends ArrayAdapter { 181 | private Activity context; 182 | private List list; 183 | 184 | public FileListAdapter(Activity context, List objects) { 185 | super((Context) context, R.layout.train_file_list_content, objects); 186 | this.context = context; 187 | this.list = objects; 188 | } 189 | 190 | @Override 191 | public View getView(int position, View convertView, ViewGroup parent) { 192 | View rowView = convertView; 193 | 194 | if (rowView == null) { 195 | LayoutInflater inflater = this.context.getLayoutInflater(); 196 | rowView = inflater.inflate(R.layout.train_file_list_content, null, true); 197 | 198 | } 199 | TextView txtTitle = (TextView) rowView.findViewById(R.id.tvFileName); 200 | CheckBox chkSelected = (CheckBox) rowView.findViewById(R.id.checkBoxSelected); 201 | 202 | TrainFile file = this.list.get(position); 203 | txtTitle.setText(file.getName()); 204 | chkSelected.setChecked(file.isSelected()); 205 | 206 | return rowView; 207 | } 208 | 209 | @Override 210 | public int getCount() { 211 | return list != null ? list.size() : 0; 212 | } 213 | 214 | @Override 215 | public TrainFile getItem(int position) { 216 | return list.get(position); 217 | } 218 | } 219 | 220 | } 221 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/activities/WorkoutComparator.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.activities; 2 | 3 | 4 | import com.trainerdb.trainerandroid.data.Workout; 5 | 6 | import java.util.Comparator; 7 | 8 | /** 9 | * Created by Daniel on 09/07/2016. 10 | */ 11 | public class WorkoutComparator implements Comparator { 12 | 13 | WorkoutListOptions options; 14 | 15 | public WorkoutComparator(WorkoutListOptions options) { 16 | this.options = options; 17 | } 18 | 19 | public static int compare(long lhs, long rhs) { 20 | return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1); 21 | } 22 | 23 | @Override 24 | public int compare(Workout x, Workout y) { 25 | // TODO: Handle null x or y values 26 | for (WorkoutListParent parent : options.getList()) { 27 | for (WorkoutListItem item : parent.filters) { 28 | if (item.active) { 29 | switch (item.type) { 30 | case sortName: 31 | if (item.asc) { 32 | int value = x.name.compareTo(y.name); 33 | if (value != 0) 34 | return value; 35 | } else { 36 | int value = y.name.compareTo(x.name); 37 | if (value != 0) 38 | return value; 39 | } 40 | case sortDuration: 41 | if (item.asc) { 42 | int value = compare(x.totalTicks, y.totalTicks); 43 | if (value != 0) 44 | return value; 45 | } else { 46 | int value =compare(y.totalTicks, x.totalTicks); 47 | if (value != 0) 48 | return value; 49 | } 50 | case sortTss: 51 | if (item.asc) { 52 | int value = Float.compare(x.tss, y.tss); 53 | if (value != 0) 54 | return value; 55 | } else { 56 | int value = Float.compare(y.tss, x.tss); 57 | if (value != 0) 58 | return value; 59 | } 60 | case sortIntensity: 61 | if (item.asc) { 62 | int value = Float.compare(x.intensityFactor, y.intensityFactor); 63 | if (value != 0) 64 | return value; 65 | } else { 66 | int value = Float.compare(y.intensityFactor, x.intensityFactor); 67 | if (value != 0) 68 | return value; 69 | } 70 | } 71 | } 72 | } 73 | } 74 | return 0; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/activities/WorkoutDetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.activities; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.design.widget.FloatingActionButton; 6 | import android.support.design.widget.Snackbar; 7 | import android.support.v7.widget.Toolbar; 8 | import android.view.View; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.support.v7.app.ActionBar; 11 | import android.view.MenuItem; 12 | 13 | import com.trainerdb.trainerandroid.R; 14 | 15 | /** 16 | * An activity representing a single Workout detail screen. This 17 | * activity is only used narrow width devices. On tablet-size devices, 18 | * item details are presented side-by-side with a list of items 19 | * in a {@link WorkoutListActivity}. 20 | */ 21 | public class WorkoutDetailActivity extends AppCompatActivity { 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_workout_detail); 27 | Toolbar toolbar = (Toolbar) findViewById(R.id.detail_toolbar); 28 | setSupportActionBar(toolbar); 29 | 30 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 31 | fab.setOnClickListener(new View.OnClickListener() { 32 | @Override 33 | public void onClick(View view) { 34 | Intent intent = new Intent(view.getContext(), TrainActivity.class); 35 | intent.putExtra("workout_key", getIntent().getStringExtra(WorkoutDetailFragment.ARG_ITEM_ID)); 36 | 37 | view.getContext().startActivity(intent); 38 | } 39 | }); 40 | 41 | // Show the Up button in the action bar. 42 | ActionBar actionBar = getSupportActionBar(); 43 | if (actionBar != null) { 44 | actionBar.setDisplayHomeAsUpEnabled(true); 45 | } 46 | 47 | // savedInstanceState is non-null when there is fragment state 48 | // saved from previous configurations of this activity 49 | // (e.g. when rotating the screen from portrait to landscape). 50 | // In this case, the fragment will automatically be re-added 51 | // to its container so we don't need to manually add it. 52 | // For more information, see the Fragments API guide at: 53 | // 54 | // http://developer.android.com/guide/components/fragments.html 55 | // 56 | if (savedInstanceState == null) { 57 | // Create the detail fragment and add it to the activity 58 | // using a fragment transaction. 59 | Bundle arguments = new Bundle(); 60 | arguments.putString(WorkoutDetailFragment.ARG_ITEM_ID, 61 | getIntent().getStringExtra(WorkoutDetailFragment.ARG_ITEM_ID)); 62 | WorkoutDetailFragment fragment = new WorkoutDetailFragment(); 63 | fragment.setArguments(arguments); 64 | getSupportFragmentManager().beginTransaction() 65 | .add(R.id.workout_detail_container, fragment) 66 | .commit(); 67 | } 68 | } 69 | 70 | @Override 71 | public boolean onOptionsItemSelected(MenuItem item) { 72 | int id = item.getItemId(); 73 | if (id == android.R.id.home) { 74 | // This ID represents the Home or Up button. In the case of this 75 | // activity, the Up button is shown. For 76 | // more details, see the Navigation pattern on Android Design: 77 | // 78 | // http://developer.android.com/design/patterns/navigation.html#up-vs-back 79 | // 80 | //navigateUpTo(new Intent(this, WorkoutListActivity.class)); 81 | super.onBackPressed(); 82 | return true; 83 | } 84 | return super.onOptionsItemSelected(item); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/activities/WorkoutDetailFragment.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.activities; 2 | 3 | import android.app.Activity; 4 | import android.support.design.widget.CollapsingToolbarLayout; 5 | import android.os.Bundle; 6 | import android.support.v4.app.Fragment; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | 12 | import com.trainerdb.trainerandroid.R; 13 | import com.trainerdb.trainerandroid.TrainApplication; 14 | import com.trainerdb.trainerandroid.WorkoutView; 15 | import com.trainerdb.trainerandroid.data.Workout; 16 | import com.trainerdb.trainerandroid.data.WorkoutData; 17 | import com.trainerdb.trainerandroid.data.WorkoutService; 18 | 19 | /** 20 | * A fragment representing a single Workout detail screen. 21 | * This fragment is either contained in a {@link WorkoutListActivity} 22 | * in two-pane mode (on tablets) or a {@link WorkoutDetailActivity} 23 | * on handsets. 24 | */ 25 | public class WorkoutDetailFragment extends Fragment { 26 | /** 27 | * The fragment argument representing the item ID that this fragment 28 | * represents. 29 | */ 30 | public static final String ARG_ITEM_ID = "item_id"; 31 | 32 | /** 33 | * The dummy content this fragment is presenting. 34 | */ 35 | private Workout mItem; 36 | private WorkoutData mData; 37 | 38 | /** 39 | * Mandatory empty constructor for the fragment manager to instantiate the 40 | * fragment (e.g. upon screen orientation changes). 41 | */ 42 | public WorkoutDetailFragment() { 43 | } 44 | 45 | @Override 46 | public void onCreate(Bundle savedInstanceState) { 47 | super.onCreate(savedInstanceState); 48 | 49 | if (getArguments().containsKey(ARG_ITEM_ID)) { 50 | // Load the dummy content specified by the fragment 51 | // arguments. In a real-world scenario, use a Loader 52 | // to load content from a content provider. 53 | mItem = WorkoutService.WORKOUT_MAP.get(getArguments().getString(ARG_ITEM_ID)); 54 | mData = WorkoutService.WORKOUT_DATA_MAP.get(getArguments().getString(ARG_ITEM_ID)); 55 | 56 | Activity activity = this.getActivity(); 57 | CollapsingToolbarLayout appBarLayout = (CollapsingToolbarLayout) activity.findViewById(R.id.toolbar_layout); 58 | WorkoutView wokroutView = (WorkoutView) activity.findViewById(R.id.workout_detail); 59 | 60 | if (appBarLayout != null) { 61 | appBarLayout.setTitle(mItem.name); 62 | wokroutView.setWorkout(mItem); 63 | } 64 | } 65 | } 66 | 67 | @Override 68 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 69 | Bundle savedInstanceState) { 70 | View rootView = inflater.inflate(R.layout.workout_detail, container, false); 71 | 72 | // Show the dummy content as text in a TextView. 73 | if (mItem != null) { 74 | //((TextView) rootView.findViewById(R.id.workout_detail)).setText(mItem.name); 75 | ((TextView) rootView.findViewById(R.id.tvWorkoutDetailName)).setText(mItem.name); 76 | ((TextView) rootView.findViewById(R.id.tvWorkoutDetailDescription)).setText(mData.description); 77 | ((TextView) rootView.findViewById(R.id.tvWorkoutDetailGoal)).setText(mData.goals); 78 | ((TextView) rootView.findViewById(R.id.tvWorkoutDetailDuration)).setText(TrainApplication.formatTime(mData.totalTicks)); 79 | ((TextView) rootView.findViewById(R.id.tvWorkoutDetailIf)).setText(String.valueOf(mData.intensityFactor)); 80 | ((TextView) rootView.findViewById(R.id.tvWorkoutDetailTss)).setText(String.valueOf(mData.tss)); 81 | } 82 | 83 | return rootView; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/activities/WorkoutFilterActivity.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.activities; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Parcelable; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.os.Bundle; 9 | import android.support.v7.widget.Toolbar; 10 | import android.view.LayoutInflater; 11 | import android.view.Menu; 12 | import android.view.MenuItem; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.BaseExpandableListAdapter; 16 | import android.widget.CheckBox; 17 | import android.widget.ExpandableListView; 18 | import android.widget.TextView; 19 | import android.widget.ToggleButton; 20 | 21 | import com.trainerdb.trainerandroid.R; 22 | 23 | import java.util.List; 24 | 25 | public class WorkoutFilterActivity extends AppCompatActivity { 26 | 27 | WorkoutListOptions options; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_workout_filter); 33 | 34 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 35 | setSupportActionBar(toolbar); 36 | toolbar.setTitle(getTitle()); 37 | 38 | if (savedInstanceState == null) { 39 | Intent intent = this.getIntent(); 40 | this.options = intent.getParcelableExtra("options"); 41 | } else { 42 | this.options = (WorkoutListOptions)savedInstanceState.getSerializable("options"); 43 | } 44 | if (options == null) { 45 | options = new WorkoutListOptions(); 46 | options.prepare(); 47 | } 48 | 49 | final ExpandableListView expListView = (ExpandableListView) findViewById(R.id.listFilter); 50 | FilterListAdapter expListAdapter = new FilterListAdapter(this, options.getList()); 51 | expListView.setAdapter(expListAdapter); 52 | } 53 | 54 | @Override 55 | public boolean onCreateOptionsMenu(Menu menu) { 56 | getMenuInflater().inflate(R.menu.toolbar_filter, menu); 57 | return true; 58 | } 59 | 60 | @Override 61 | public boolean onOptionsItemSelected(MenuItem item) { 62 | int id = item.getItemId(); 63 | if (id == R.id.action_filter_apply) { 64 | Intent resultIntent = new Intent(); 65 | resultIntent.putExtra("options", (Parcelable) options); 66 | setResult(Activity.RESULT_OK, resultIntent); 67 | finish(); 68 | return true; 69 | } else if (id == R.id.action_filter_cancel) { 70 | finish(); 71 | } 72 | return super.onOptionsItemSelected(item); 73 | } 74 | 75 | public class FilterListAdapter extends BaseExpandableListAdapter { 76 | 77 | private Activity context; 78 | private List list; 79 | 80 | public FilterListAdapter(Activity context, List listPlanCategories) { 81 | this.context = context; 82 | this.list = listPlanCategories; 83 | } 84 | 85 | public Object getChild(int groupPosition, int childPosition) { 86 | return list.get(groupPosition).filters.get(childPosition); 87 | } 88 | 89 | public long getChildId(int groupPosition, int childPosition) { 90 | return childPosition; 91 | } 92 | 93 | @Override 94 | public int getChildType(int groupPosition, int childPosition) { 95 | return ((WorkoutListParent)getGroup(groupPosition)).sort ? 1 : 0; 96 | } 97 | 98 | @Override 99 | public int getChildTypeCount(){ 100 | return 2; 101 | } 102 | 103 | @Override 104 | public View getChildView(final int groupPosition, final int childPosition, 105 | boolean isLastChild, View convertView, ViewGroup parent) { 106 | 107 | final WorkoutListItem item = (WorkoutListItem) getChild(groupPosition, childPosition); 108 | final WorkoutListParent parentItem = (WorkoutListParent) getGroup(groupPosition); 109 | 110 | int type = getChildType(groupPosition, childPosition); 111 | if (type == 1) { 112 | 113 | if (convertView == null) { 114 | LayoutInflater inflater = context.getLayoutInflater(); 115 | convertView = inflater.inflate(R.layout.workout_filter_sort_content, null); 116 | } 117 | 118 | TextView tvFilterItemName = (TextView) convertView.findViewById(R.id.tvSortName); 119 | final ToggleButton toggleSortAsc = (ToggleButton) convertView.findViewById(R.id.toggleSortAsc); 120 | final ToggleButton toggleSortDesc = (ToggleButton) convertView.findViewById(R.id.toggleSortDesc); 121 | 122 | toggleSortAsc.setTextOn(item.ascName); 123 | toggleSortAsc.setTextOff(item.ascName); 124 | toggleSortDesc.setTextOn(item.descName); 125 | toggleSortDesc.setTextOff(item.descName); 126 | 127 | toggleSortAsc.setChecked(item.active && item.asc); 128 | toggleSortDesc.setChecked(item.active && !item.asc); 129 | 130 | toggleSortAsc.setOnClickListener(new View.OnClickListener() { 131 | @Override 132 | public void onClick(View v) { 133 | if (toggleSortAsc.isChecked()) { 134 | toggleSortDesc.setChecked(false); 135 | item.active = true; 136 | item.asc = true; 137 | } else { 138 | item.active = false; 139 | } 140 | } 141 | }); 142 | 143 | toggleSortDesc.setOnClickListener(new View.OnClickListener() { 144 | @Override 145 | public void onClick(View v) { 146 | if (toggleSortDesc.isChecked()) { 147 | toggleSortAsc.setChecked(false); 148 | item.active = true; 149 | item.asc = false; 150 | } else { 151 | item.active = false; 152 | } 153 | } 154 | }); 155 | 156 | tvFilterItemName.setText(item.name); 157 | } 158 | else { 159 | if (convertView == null) { 160 | LayoutInflater inflater = context.getLayoutInflater(); 161 | convertView = inflater.inflate(R.layout.workout_filter_list_content, null); 162 | } 163 | TextView tvFilterItemName = (TextView) convertView.findViewById(R.id.tvFilterItemName); 164 | final CheckBox checkFilterItem = (CheckBox) convertView.findViewById(R.id.checkFilterItem); 165 | checkFilterItem.setChecked(item.active); 166 | checkFilterItem.setOnClickListener(new View.OnClickListener() { 167 | @Override 168 | public void onClick(View v) { 169 | item.active = checkFilterItem.isChecked(); 170 | } 171 | }); 172 | 173 | tvFilterItemName.setText(item.name); 174 | } 175 | return convertView; 176 | } 177 | 178 | public int getChildrenCount(int groupPosition) { 179 | return list.get(groupPosition).filters.size(); // laptopCollections.get(laptops.get(groupPosition)).size(); 180 | } 181 | 182 | public Object getGroup(int groupPosition) { 183 | return list.get(groupPosition); 184 | } 185 | 186 | public int getGroupCount() { 187 | return list.size(); 188 | } 189 | 190 | public long getGroupId(int groupPosition) { 191 | return groupPosition; 192 | } 193 | 194 | public View getGroupView(int groupPosition, boolean isExpanded, 195 | View convertView, ViewGroup parent) { 196 | WorkoutListParent parentItem = (WorkoutListParent) getGroup(groupPosition); 197 | if (convertView == null) { 198 | LayoutInflater infalInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 199 | convertView = infalInflater.inflate(R.layout.workout_filter_group, null); 200 | } 201 | TextView item = (TextView) convertView.findViewById(R.id.tvParentItemName); 202 | //item.setTypeface(null, Typeface.BOLD); 203 | item.setText(parentItem.name); 204 | return convertView; 205 | } 206 | 207 | public boolean hasStableIds() { 208 | return true; 209 | } 210 | 211 | public boolean isChildSelectable(int groupPosition, int childPosition) { 212 | return true; 213 | } 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/activities/WorkoutListItem.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.activities; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | /** 7 | * Created by Daniel on 09/07/2016. 8 | */ 9 | public class WorkoutListItem implements Parcelable { 10 | public String name; 11 | public String ascName; 12 | public String descName; 13 | public WorkoutListOptions.ItemType type; 14 | public boolean active = false; 15 | public boolean asc = true; 16 | 17 | 18 | public WorkoutListItem() { 19 | 20 | } 21 | protected WorkoutListItem(Parcel in) { 22 | name = in.readString(); 23 | ascName = in.readString(); 24 | descName = in.readString(); 25 | type = (WorkoutListOptions.ItemType) in.readValue(WorkoutListOptions.ItemType.class.getClassLoader()); 26 | active = in.readByte() != 0x00; 27 | asc = in.readByte() != 0x00; 28 | } 29 | 30 | @Override 31 | public int describeContents() { 32 | return 0; 33 | } 34 | 35 | @Override 36 | public void writeToParcel(Parcel dest, int flags) { 37 | dest.writeString(name); 38 | dest.writeString(ascName); 39 | dest.writeString(descName); 40 | dest.writeValue(type); 41 | dest.writeByte((byte) (active ? 0x01 : 0x00)); 42 | dest.writeByte((byte) (asc ? 0x01 : 0x00)); 43 | } 44 | 45 | @SuppressWarnings("unused") 46 | public static final Creator CREATOR = new Creator() { 47 | @Override 48 | public WorkoutListItem createFromParcel(Parcel in) { 49 | return new WorkoutListItem(in); 50 | } 51 | 52 | @Override 53 | public WorkoutListItem[] newArray(int size) { 54 | return new WorkoutListItem[size]; 55 | } 56 | }; 57 | } -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/activities/WorkoutListOptions.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.activities; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import java.io.Serializable; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Created by dcotrim on 22/09/2016. 12 | */ 13 | public class WorkoutListOptions implements Serializable, Parcelable { 14 | public enum ItemType { 15 | sortName, sortDuration, sortIntensity, sortTss, 16 | filterZoneEndurance, filterZoneTempo, filterZoneSweetSpot, filterZoneThreshold, filterZoneVo2, filterZoneAnaerobic, filterZoneSprint, filterZoneStrength, 17 | filterDurationLess1h, filterDuration60_90, filterDuration90_2h, filterDuration2_3h, filterDuration3hmore, 18 | filterFavorite 19 | } 20 | 21 | private List list; 22 | 23 | public WorkoutListOptions() { 24 | 25 | } 26 | 27 | public void prepare() { 28 | list = new ArrayList<>(); 29 | 30 | WorkoutListParent sort = new WorkoutListParent("Sort by", true); 31 | sort.filters.add(sort.newSort(ItemType.sortName, "Name", "A - Z", "Z - A")); 32 | sort.filters.add(sort.newSort(ItemType.sortDuration, "Duration", "Shortest", "Longest")); 33 | sort.filters.add(sort.newSort(ItemType.sortIntensity, "Intensity", "Lowest", "Highest")); 34 | sort.filters.add(sort.newSort(ItemType.sortTss, "TSS", "Lowest", "Highest")); 35 | list.add(sort); 36 | 37 | WorkoutListParent filterZone = new WorkoutListParent("Filter by tags", false); 38 | filterZone.filters.add(filterZone.newFilter(ItemType.filterZoneEndurance, "Endurance", true)); 39 | filterZone.filters.add(filterZone.newFilter(ItemType.filterZoneTempo, "Tempo", true)); 40 | filterZone.filters.add(filterZone.newFilter(ItemType.filterZoneSweetSpot, "Sweet Spot", true)); 41 | filterZone.filters.add(filterZone.newFilter(ItemType.filterZoneThreshold, "Threshold", true)); 42 | filterZone.filters.add(filterZone.newFilter(ItemType.filterZoneVo2, "VO2 Max", true)); 43 | filterZone.filters.add(filterZone.newFilter(ItemType.filterZoneAnaerobic, "Anaerobic", true)); 44 | filterZone.filters.add(filterZone.newFilter(ItemType.filterZoneSprint, "Sprint", true)); 45 | filterZone.filters.add(filterZone.newFilter(ItemType.filterZoneStrength, "Strength", true)); 46 | 47 | list.add(filterZone); 48 | 49 | WorkoutListParent filterDuration = new WorkoutListParent("Filter by duration", false); 50 | filterDuration.filters.add(filterDuration.newFilter(ItemType.filterDurationLess1h, "Less than 1h", true)); 51 | filterDuration.filters.add(filterDuration.newFilter(ItemType.filterDuration60_90, "60 - 90min", true)); 52 | filterDuration.filters.add(filterDuration.newFilter(ItemType.filterDuration90_2h, "90min - 2h", true)); 53 | filterDuration.filters.add(filterDuration.newFilter(ItemType.filterDuration2_3h, "2 - 3h", true)); 54 | filterDuration.filters.add(filterDuration.newFilter(ItemType.filterDuration3hmore, "More than 3h", true)); 55 | list.add(filterDuration); 56 | 57 | WorkoutListParent filterOptions = new WorkoutListParent("Filter Options", false); 58 | filterOptions.filters.add(filterDuration.newFilter(ItemType.filterFavorite, "Show only Favorites", false)); 59 | list.add(filterOptions); 60 | } 61 | 62 | public List getList() { 63 | return list; 64 | } 65 | 66 | protected WorkoutListOptions(Parcel in) { 67 | if (in.readByte() == 0x01) { 68 | list = new ArrayList(); 69 | in.readList(list, WorkoutListParent.class.getClassLoader()); 70 | } else { 71 | list = null; 72 | } 73 | } 74 | 75 | @Override 76 | public int describeContents() { 77 | return 0; 78 | } 79 | 80 | @Override 81 | public void writeToParcel(Parcel dest, int flags) { 82 | if (list == null) { 83 | dest.writeByte((byte) (0x00)); 84 | } else { 85 | dest.writeByte((byte) (0x01)); 86 | dest.writeList(list); 87 | } 88 | } 89 | 90 | @SuppressWarnings("unused") 91 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 92 | @Override 93 | public WorkoutListOptions createFromParcel(Parcel in) { 94 | return new WorkoutListOptions(in); 95 | } 96 | 97 | @Override 98 | public WorkoutListOptions[] newArray(int size) { 99 | return new WorkoutListOptions[size]; 100 | } 101 | }; 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/activities/WorkoutListParent.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.activities; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * Created by dcotrim on 22/09/2016. 11 | */ 12 | public class WorkoutListParent implements Parcelable { 13 | public String name; 14 | public List filters; 15 | public boolean sort = false; 16 | 17 | public WorkoutListParent(String name, boolean sort) { 18 | this.name = name; 19 | this.sort = sort; 20 | filters = new ArrayList<>(); 21 | } 22 | 23 | public WorkoutListItem newFilter(WorkoutListOptions.ItemType type, String name, boolean active) { 24 | WorkoutListItem i = new WorkoutListItem(); 25 | i.type = type; 26 | i.name = name; 27 | i.active = active; 28 | return i; 29 | } 30 | 31 | public WorkoutListItem newSort(WorkoutListOptions.ItemType type, String name, String ascName, String descName) { 32 | WorkoutListItem i = new WorkoutListItem(); 33 | i.type = type; 34 | i.name = name; 35 | i.ascName = ascName; 36 | i.descName = descName; 37 | i.active = false; 38 | return i; 39 | } 40 | 41 | protected WorkoutListParent(Parcel in) { 42 | name = in.readString(); 43 | sort = in.readByte() != 0x00; 44 | if (in.readByte() == 0x01) { 45 | filters = new ArrayList(); 46 | in.readList(filters, WorkoutListItem.class.getClassLoader()); 47 | } else { 48 | filters = null; 49 | } 50 | } 51 | 52 | @Override 53 | public int describeContents() { 54 | return 0; 55 | } 56 | 57 | @Override 58 | public void writeToParcel(Parcel dest, int flags) { 59 | dest.writeString(name); 60 | dest.writeByte((byte) (sort ? 0x01 : 0x00)); 61 | if (filters == null) { 62 | dest.writeByte((byte) (0x00)); 63 | } else { 64 | dest.writeByte((byte) (0x01)); 65 | dest.writeList(filters); 66 | } 67 | } 68 | 69 | @SuppressWarnings("unused") 70 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 71 | @Override 72 | public WorkoutListParent createFromParcel(Parcel in) { 73 | return new WorkoutListParent(in); 74 | } 75 | 76 | @Override 77 | public WorkoutListParent[] newArray(int size) { 78 | return new WorkoutListParent[size]; 79 | } 80 | }; 81 | } -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/data/ErgFile.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.data; 2 | 3 | import android.graphics.PointF; 4 | 5 | import com.trainerdb.trainerandroid.TrainApplication; 6 | 7 | import java.io.Serializable; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by dcotrim on 22/06/2016. 13 | */ 14 | public class ErgFile implements Serializable { 15 | private List coursePoints; 16 | private List textPoints; 17 | private List lapPoints; 18 | private long duration = 0; 19 | 20 | public long getDuration() { 21 | return duration; 22 | } 23 | 24 | public List getCoursePoints() { 25 | return coursePoints; 26 | } 27 | 28 | public List getLapPoints() { 29 | return lapPoints; 30 | } 31 | 32 | public List getTextPoints() { 33 | return textPoints; 34 | } 35 | 36 | public ErgFile(List points, List laps, List texts) { 37 | //List lines = new ArrayList(); 38 | coursePoints = (points == null ? new ArrayList() : points); 39 | textPoints = (texts == null ? new ArrayList() : texts);; 40 | lapPoints = (laps == null ? new ArrayList() : laps);; 41 | 42 | duration = (long) coursePoints.get(coursePoints.size() - 1).t; 43 | 44 | int ftp = TrainApplication.getFTP(); 45 | for (ErgPoint point: coursePoints) { 46 | point.w = (int) ((point.w / 100) * ftp); 47 | } 48 | 49 | Boolean newLapWorkout = false; 50 | if (this.lapPoints.size() == 0) 51 | newLapWorkout = true; 52 | else { 53 | LapPoint first = this.lapPoints.get(0); 54 | if (first.x != 0 || first.y != duration) 55 | newLapWorkout = true; 56 | } 57 | if (newLapWorkout) 58 | newLap(0, duration, 0, "Workout"); 59 | } 60 | 61 | private void newLap(long x, long y, int num, String name) { 62 | LapPoint newLap = new LapPoint(); 63 | newLap.x = x; 64 | newLap.y = y; 65 | newLap.name = name; 66 | newLap.lapNum = num; 67 | lapPoints.add(newLap); 68 | } 69 | 70 | public int lapAt(long x) { 71 | if (x < 0 || x > duration) return -100; // out of bounds!!! 72 | 73 | int lapNum = 0; 74 | long temp = 0; 75 | if (lapPoints.size() > 0) { 76 | for (int i = 0; i < lapPoints.size(); i++) { 77 | LapPoint lap = lapPoints.get(i); 78 | if (x >= lap.x && x <= lap.y) { 79 | if (lap.x > temp) { 80 | temp = lap.x; 81 | lapNum = lap.lapNum; 82 | } 83 | } 84 | } 85 | } 86 | return lapNum; 87 | } 88 | 89 | public String textAt(long x) { 90 | String text = ""; 91 | if (x < 0 || x > duration) return "-1"; // out of bounds!!! 92 | 93 | long groupStart = -1; 94 | long start = 0; 95 | if (textPoints.size() > 0) { 96 | for (int i = 0; i < textPoints.size(); i++) { 97 | TextPoint tPoint = textPoints.get(i); 98 | if (groupStart != tPoint.x) { 99 | groupStart = tPoint.x; 100 | start = groupStart; 101 | } else 102 | start += tPoint.duration; 103 | 104 | if (x < start) continue; 105 | if (x > (start + (tPoint.duration))) continue; 106 | 107 | return tPoint.text;// + TrainApplication.formatTime(tPoint.duration + start - x); 108 | } 109 | } 110 | return text; 111 | } 112 | 113 | public double wattsAt(long x) { 114 | if (x < 0 || x > duration) return -100; 115 | 116 | int rightPoint, leftPoint; 117 | leftPoint = 0; 118 | rightPoint = 1; 119 | 120 | while (x < coursePoints.get(leftPoint).t || x > coursePoints.get(rightPoint).t) { 121 | if (x < coursePoints.get(leftPoint).t) { 122 | leftPoint--; 123 | rightPoint--; 124 | } else if (x > coursePoints.get(rightPoint).t) { 125 | leftPoint++; 126 | rightPoint++; 127 | } 128 | } 129 | 130 | if (coursePoints.get(leftPoint).w == coursePoints.get(rightPoint).w) 131 | return coursePoints.get(rightPoint).w; 132 | 133 | double deltaW = coursePoints.get(rightPoint).w - coursePoints.get(leftPoint).w; 134 | double deltaT = coursePoints.get(rightPoint).t - coursePoints.get(leftPoint).t; 135 | double offT = x - coursePoints.get(leftPoint).t; 136 | double factor = offT / deltaT; 137 | 138 | double nowW = coursePoints.get(leftPoint).w + (deltaW * factor); 139 | 140 | return nowW; 141 | } 142 | 143 | public long nextLapTime(long x) { 144 | //if (!isValid()) return -1; // not a valid ergfile 145 | 146 | long next = duration; 147 | long temp = 0; 148 | // do we need to return the Lap marker? 149 | if (lapPoints.size() > 0) { 150 | for (int i = 0; i < lapPoints.size(); i++) { 151 | LapPoint lap = lapPoints.get(i); 152 | if (x >= lap.x && x <= lap.y) { 153 | if (lap.x > temp) { 154 | temp = lap.x; 155 | next = lap.y; 156 | } 157 | } 158 | if (x < lap.x && lap.x < next) next = lap.x; 159 | if (x < lap.y && lap.y < next) next = lap.y; 160 | } 161 | } 162 | return next; // nope, no marker ahead of there 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/data/ErgPoint.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.data; 2 | 3 | /** 4 | * Created by dcotrim on 21/09/2016. 5 | */ 6 | public class ErgPoint { 7 | public double t; 8 | public double w; 9 | public ErgPoint(double t, double w) { 10 | this.t = t; 11 | this.w = w; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/data/LapPoint.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.data; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by dcotrim on 21/09/2016. 7 | */ 8 | public class LapPoint implements Serializable { 9 | public long x; 10 | public long y; 11 | public int lapNum; 12 | public String name; 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/data/PowerZone.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.data; 2 | 3 | /** 4 | * Created by dcotrim on 18/07/2016. 5 | */ 6 | public class PowerZone { 7 | String description; 8 | int low, high; 9 | 10 | public int getHigh() { 11 | return high; 12 | } 13 | 14 | public int getLow() { 15 | return low; 16 | } 17 | 18 | public String getDescription() { 19 | return description; 20 | } 21 | 22 | public void setDescription(String description) { 23 | this.description = description; 24 | } 25 | 26 | public void setHigh(int high) { 27 | this.high = high; 28 | } 29 | 30 | public void setLow(int low) { 31 | this.low = low; 32 | } 33 | 34 | public PowerZone() { 35 | 36 | } 37 | 38 | public PowerZone(String description, int low, int high) { 39 | this.description = description; 40 | this.low = low; 41 | this.high = high; 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/data/TextPoint.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.data; 2 | 3 | /** 4 | * Created by dcotrim on 21/09/2016. 5 | */ 6 | public class TextPoint { 7 | public long x; 8 | public int duration; 9 | public String text; 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/data/Workout.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.data; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Created by dcotrim on 20/09/2016. 8 | */ 9 | public class Workout { 10 | public String key; 11 | public String name; 12 | public String source; 13 | public String author; 14 | public float intensityFactor; 15 | public long totalTicks; 16 | public long createdAt; 17 | public long tss; 18 | public Map tags = new HashMap<>(); 19 | 20 | public Workout() { 21 | 22 | } 23 | @Override 24 | public String toString() { 25 | return name.toString()+"#"+String.valueOf(tss)+"#"+String.valueOf(intensityFactor); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/data/WorkoutData.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.data; 2 | 3 | import com.trainerdb.trainerandroid.Compression; 4 | 5 | import org.json.JSONArray; 6 | import org.json.JSONObject; 7 | 8 | import java.io.Serializable; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * Created by dcotrim on 20/09/2016. 14 | */ 15 | public class WorkoutData implements Serializable{ 16 | public String key; 17 | public String description; 18 | public String goals; 19 | public String name; 20 | public String source; 21 | public String pointsDeflate; 22 | 23 | public float intensityFactor; 24 | public long totalTicks; 25 | public long createdAt; 26 | public long tss; 27 | 28 | private transient ErgFile erg; 29 | 30 | public ErgFile getErg() { 31 | if (this.erg == null) { 32 | List ergPoints = new ArrayList<>(); 33 | String inflated = Compression.decompress(this.pointsDeflate); 34 | try { 35 | JSONArray array = new JSONArray(inflated); 36 | for (int i = 0; i < array.length(); i++) { 37 | JSONObject jObject = array.getJSONObject(i); 38 | ergPoints.add(new ErgPoint(jObject.getDouble("t") * 1000, jObject.getDouble("w"))); 39 | } 40 | } catch (Exception ex) { 41 | } 42 | 43 | this.erg = new ErgFile(ergPoints, null, null); 44 | } 45 | return this.erg; 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/data/WorkoutService.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.data; 2 | 3 | import com.google.firebase.database.DataSnapshot; 4 | import com.google.firebase.database.DatabaseError; 5 | import com.google.firebase.database.DatabaseReference; 6 | import com.google.firebase.database.FirebaseDatabase; 7 | import com.google.firebase.database.ValueEventListener; 8 | import com.trainerdb.trainerandroid.IGetAsyncListener; 9 | 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | /** 16 | * Created by dcotrim on 20/09/2016. 17 | */ 18 | public class WorkoutService { 19 | 20 | public static final List WORKOUTS = new ArrayList(); 21 | public static final Map WORKOUT_MAP = new HashMap(); 22 | 23 | public static final Map WORKOUT_DATA_MAP = new HashMap(); 24 | 25 | private static final int COUNT = 25; 26 | 27 | static { 28 | // Add some sample items. 29 | //getData(null); 30 | } 31 | 32 | public static void getWorkoutData(String key, final IGetAsyncListener complete) { 33 | if (WORKOUT_DATA_MAP.containsKey(key)) { 34 | if (complete != null) 35 | complete.onDataGet(true, WORKOUT_DATA_MAP.get(key)); 36 | return; 37 | } 38 | FirebaseDatabase database = FirebaseDatabase.getInstance(); 39 | DatabaseReference myRef = database.getReference("workoutCreator").child(key); 40 | myRef.addListenerForSingleValueEvent(new ValueEventListener() { 41 | @Override 42 | public void onDataChange(DataSnapshot dataSnapshot) { 43 | WorkoutData item = dataSnapshot.getValue(WorkoutData.class); 44 | WORKOUT_DATA_MAP.put(item.key, item); 45 | 46 | if (complete != null) { 47 | complete.onDataGet(true, item); 48 | } 49 | } 50 | 51 | @Override 52 | public void onCancelled(DatabaseError databaseError) { 53 | 54 | } 55 | }); 56 | } 57 | 58 | public static void getWorkouts(final IGetAsyncListener complete) { 59 | if (WORKOUTS.size() > 0) { 60 | if (complete != null) 61 | complete.onDataGet(true, WORKOUTS); 62 | return; 63 | } 64 | FirebaseDatabase database = FirebaseDatabase.getInstance(); 65 | DatabaseReference myRef = database.getReference("workouts"); 66 | myRef.orderByChild("totalTicks").addListenerForSingleValueEvent(new ValueEventListener() { 67 | @Override 68 | public void onDataChange(DataSnapshot dataSnapshot) { 69 | for (DataSnapshot messageSnapshot: dataSnapshot.getChildren()) { 70 | Workout workout = messageSnapshot.getValue(Workout.class); 71 | addItem(workout); 72 | } 73 | if (complete != null) { 74 | complete.onDataGet(true, WORKOUTS); 75 | } 76 | } 77 | 78 | @Override 79 | public void onCancelled(DatabaseError databaseError) { 80 | 81 | } 82 | }); 83 | } 84 | 85 | private static void addItem(Workout item) { 86 | WORKOUTS.add(item); 87 | WORKOUT_MAP.put(item.key, item); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/listeners/Ant.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.listeners; 2 | 3 | import com.dsi.ant.plugins.antplus.pcc.defines.DeviceState; 4 | import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc; 5 | import com.dsi.ant.plugins.antplus.pccbase.PccReleaseHandle; 6 | 7 | /** 8 | * Created by Daniel on 25/06/2016. 9 | */ 10 | public abstract class Ant //extends Base 11 | { 12 | protected PccReleaseHandle releaseHandle; //Handle class 13 | protected int deviceNumber = 0; 14 | AntPlusController controller; 15 | 16 | //setup listeners and logging 17 | public Ant(int pDeviceNumber, AntPlusController controller) 18 | { 19 | deviceNumber = pDeviceNumber; 20 | this.controller = controller; 21 | } 22 | 23 | 24 | public AntPluginPcc.IDeviceStateChangeReceiver mDeviceStateChangeReceiver = new AntPluginPcc.IDeviceStateChangeReceiver() { 25 | @Override 26 | public void onDeviceStateChange(final DeviceState newDeviceState) { 27 | //if we lose a device zero out its values 28 | controller.setDeviceStatus(deviceNumber + ": " + newDeviceState); 29 | if (newDeviceState.equals(DeviceState.DEAD)) { 30 | zeroReadings(); 31 | } 32 | else if (newDeviceState.equals(DeviceState.SEARCHING)){ 33 | zeroReadings(); 34 | } 35 | } 36 | }; 37 | 38 | abstract protected void zeroReadings(); 39 | 40 | abstract protected void requestAccess(); 41 | 42 | public void stop() { 43 | if (releaseHandle != null) { 44 | releaseHandle.close(); 45 | } 46 | releaseHandle = null; 47 | } 48 | 49 | 50 | } 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/listeners/AntCadence.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.listeners; 2 | 3 | import com.dsi.ant.plugins.antplus.pcc.AntPlusBikeCadencePcc; 4 | import com.dsi.ant.plugins.antplus.pcc.defines.DeviceState; 5 | import com.dsi.ant.plugins.antplus.pcc.defines.EventFlag; 6 | import com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult; 7 | import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc; 8 | import com.trainerdb.trainerandroid.TrainApplication; 9 | 10 | import java.math.BigDecimal; 11 | import java.util.EnumSet; 12 | 13 | /** 14 | * Created by Daniel on 25/06/2016. 15 | */ 16 | public class AntCadence extends Ant { 17 | public AntPluginPcc.IPluginAccessResultReceiver mResultReceiver; 18 | 19 | public AntCadence(int pDeviceNumber, final AntPlusController controller) { 20 | super(pDeviceNumber, controller); 21 | 22 | mResultReceiver = new AntPluginPcc.IPluginAccessResultReceiver() { 23 | //Handle the result, connecting to events on success or reporting failure to user. 24 | @Override 25 | public void onResultReceived(AntPlusBikeCadencePcc result, RequestAccessResult resultCode, DeviceState initialDeviceStateCode) { 26 | controller.setDeviceStatus("AntCadence: " + resultCode); 27 | if (resultCode == com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult.SUCCESS) { 28 | deviceNumber = result.getAntDeviceNumber(); 29 | result.subscribeRawCadenceDataEvent( 30 | new AntPlusBikeCadencePcc.IRawCadenceDataReceiver() { 31 | @Override 32 | public void onNewRawCadenceData(final long estTimestamp, final EnumSet eventFlags, final BigDecimal timestampOfLastEvent, final long cumulativeRevolutions) { 33 | //controller.setHr(computedHeartRate); 34 | } 35 | } 36 | ); 37 | 38 | result.subscribeCalculatedCadenceEvent( 39 | new AntPlusBikeCadencePcc.ICalculatedCadenceReceiver() { 40 | @Override 41 | public void onNewCalculatedCadence(final long estTimestamp, final EnumSet eventFlags, final BigDecimal calculatedCadence) { 42 | controller.setCadence(calculatedCadence.doubleValue()); 43 | } 44 | } 45 | ); 46 | } else if (resultCode == com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult.SEARCH_TIMEOUT) { 47 | requestAccess(); 48 | } 49 | } 50 | }; 51 | requestAccess(); 52 | } 53 | 54 | 55 | @Override 56 | protected void requestAccess() { 57 | releaseHandle = AntPlusBikeCadencePcc.requestAccess(TrainApplication.getAppContext().getApplicationContext(), deviceNumber, 0, true, mResultReceiver, mDeviceStateChangeReceiver); 58 | } 59 | 60 | @Override 61 | protected void zeroReadings() { 62 | controller.setCadence(0); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/listeners/AntHeartRate.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.listeners; 2 | 3 | import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc; 4 | import com.dsi.ant.plugins.antplus.pcc.defines.DeviceState; 5 | import com.dsi.ant.plugins.antplus.pcc.defines.EventFlag; 6 | import com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult; 7 | import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc; 8 | import com.trainerdb.trainerandroid.TrainApplication; 9 | 10 | import java.math.BigDecimal; 11 | import java.util.EnumSet; 12 | 13 | /** 14 | * Created by Daniel on 25/06/2016. 15 | */ 16 | public class AntHeartRate extends Ant 17 | { 18 | public AntPluginPcc.IPluginAccessResultReceiver mResultReceiver; 19 | 20 | public AntHeartRate(int pDeviceNumber, final AntPlusController controller) { 21 | super(pDeviceNumber, controller); 22 | 23 | mResultReceiver = new AntPluginPcc.IPluginAccessResultReceiver() { 24 | //Handle the result, connecting to events on success or reporting failure to user. 25 | @Override 26 | public void onResultReceived(AntPlusHeartRatePcc result, RequestAccessResult resultCode, DeviceState initialDeviceState) 27 | { 28 | controller.setDeviceStatus("AntHeartRate: " + resultCode); 29 | if(resultCode == com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult.SUCCESS) { 30 | deviceNumber = result.getAntDeviceNumber(); 31 | result.subscribeHeartRateDataEvent( 32 | new AntPlusHeartRatePcc.IHeartRateDataReceiver() { 33 | @Override 34 | public void onNewHeartRateData(final long estTimestamp, EnumSet eventFlags, final int computedHeartRate, final long heartBeatCount, final BigDecimal heartBeatEventTime, final AntPlusHeartRatePcc.DataState dataState) { 35 | controller.setHr(computedHeartRate); 36 | } 37 | } 38 | ); 39 | } 40 | else { 41 | if (resultCode == com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult.SEARCH_TIMEOUT) { 42 | requestAccess(); 43 | } 44 | } 45 | } 46 | }; 47 | 48 | requestAccess(); 49 | } 50 | 51 | 52 | 53 | @Override 54 | protected void requestAccess() { 55 | releaseHandle = AntPlusHeartRatePcc.requestAccess(TrainApplication.getAppContext(), deviceNumber, 0, mResultReceiver, mDeviceStateChangeReceiver); 56 | } 57 | 58 | @Override 59 | public void zeroReadings() 60 | { 61 | controller.setHr(0); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/listeners/AntPlusController.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.listeners; 2 | 3 | import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc; 4 | import com.dsi.ant.plugins.antplus.pccbase.AsyncScanController; 5 | import com.trainerdb.trainerandroid.data.RealtimeController; 6 | import com.trainerdb.trainerandroid.train.DeviceConfiguration; 7 | import com.trainerdb.trainerandroid.train.RealtimeData; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * Created by Daniel on 25/06/2016. 14 | */ 15 | public class AntPlusController extends RealtimeController { 16 | AsyncScanController hrScanCtrl; 17 | int hr = 0; 18 | double load, cadence, speed, watts; 19 | String deviceStatus; 20 | List devices = new ArrayList<>(); 21 | 22 | public AntPlusController(DeviceConfiguration device){ 23 | super(device); 24 | } 25 | 26 | @Override 27 | public void getRealtimeData(RealtimeData rtData) { 28 | rtData.setName("ANT+"); 29 | rtData.setWatts(watts); 30 | rtData.setSpeed(speed); 31 | rtData.setCadence(cadence); 32 | rtData.setHr(hr); 33 | rtData.setLoad(load); 34 | rtData.setDeviceStatus(deviceStatus); 35 | processRealTimeData(rtData); 36 | 37 | deviceStatus = null; 38 | } 39 | 40 | @Override 41 | public void setLoad(double load) { 42 | this.load = load; 43 | } 44 | 45 | public void setHr(int hr) { 46 | this.hr = hr; 47 | } 48 | 49 | public void setCadence(double cadence) { 50 | this.cadence = cadence; 51 | } 52 | 53 | public void setSpeed(double speed) { 54 | this.speed = speed; 55 | } 56 | 57 | public void setDeviceStatus(String deviceStatus) { 58 | this.deviceStatus = deviceStatus; 59 | } 60 | 61 | 62 | @Override 63 | public void start() { 64 | devices.clear(); 65 | devices.add(new AntHeartRate(0, this)); 66 | devices.add(new AntSpeedDistance(0, this)); 67 | } 68 | 69 | @Override 70 | public void stop() { 71 | for (Ant device: devices) 72 | device.stop(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/listeners/AntSpeedDistance.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.listeners; 2 | 3 | import com.dsi.ant.plugins.antplus.pcc.AntPlusBikeCadencePcc; 4 | import com.dsi.ant.plugins.antplus.pcc.AntPlusBikeSpeedDistancePcc; 5 | import com.dsi.ant.plugins.antplus.pcc.defines.DeviceState; 6 | import com.dsi.ant.plugins.antplus.pcc.defines.EventFlag; 7 | import com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult; 8 | import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc; 9 | import com.trainerdb.trainerandroid.TrainApplication; 10 | 11 | import java.math.BigDecimal; 12 | import java.util.EnumSet; 13 | 14 | /** 15 | * Created by Daniel on 25/06/2016. 16 | */ 17 | public class AntSpeedDistance extends Ant { 18 | 19 | public AntPluginPcc.IPluginAccessResultReceiver mResultReceiver; 20 | 21 | public AntSpeedDistance(int pDeviceNumber, final AntPlusController controller) { 22 | super(pDeviceNumber, controller); 23 | 24 | mResultReceiver = new AntPluginPcc.IPluginAccessResultReceiver() { 25 | //Handle the result, connecting to events on success or reporting failure to user. 26 | @Override 27 | public void onResultReceived(AntPlusBikeSpeedDistancePcc result, RequestAccessResult resultCode, DeviceState initialDeviceState) { 28 | controller.setDeviceStatus("AntSpeedDistance: " + resultCode); 29 | if (resultCode == com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult.SUCCESS) { 30 | deviceNumber = result.getAntDeviceNumber(); 31 | 32 | result.subscribeMotionAndSpeedDataEvent( 33 | new AntPlusBikeSpeedDistancePcc.IMotionAndSpeedDataReceiver() { 34 | @Override 35 | public void onNewMotionAndSpeedData(long l, EnumSet enumSet, boolean b) { 36 | 37 | } 38 | } 39 | ); 40 | result.subscribeCalculatedSpeedEvent( 41 | new AntPlusBikeSpeedDistancePcc.CalculatedSpeedReceiver( 42 | new BigDecimal(TrainApplication.getWheelCircunference() / 1000)) { 43 | @Override 44 | public void onNewCalculatedSpeed(long estTimestamp, EnumSet eventFlags, final BigDecimal calculatedSpeed) { 45 | controller.setSpeed(calculatedSpeed.doubleValue() * 3.6); 46 | } 47 | } 48 | ); 49 | result.subscribeRawSpeedAndDistanceDataEvent( 50 | new AntPlusBikeSpeedDistancePcc.IRawSpeedAndDistanceDataReceiver() { 51 | @Override 52 | public void onNewRawSpeedAndDistanceData(final long estTimestamp, final EnumSet eventFlags, final BigDecimal timestampOfLastEvent, final long cumulativeRevolutions) { 53 | //controller.setHr(0); 54 | } 55 | } 56 | ); 57 | 58 | if (result.isSpeedAndCadenceCombinedSensor()) { 59 | AntPlusBikeCadencePcc.requestAccess(TrainApplication.getAppContext(), deviceNumber, 0, true, 60 | new AntPluginPcc.IPluginAccessResultReceiver() { 61 | @Override 62 | public void onResultReceived(AntPlusBikeCadencePcc resultCadence, RequestAccessResult resultCodeCadence, DeviceState initialDeviceStateCode) { 63 | if (resultCodeCadence == RequestAccessResult.SUCCESS) { 64 | resultCadence.subscribeCalculatedCadenceEvent( 65 | new AntPlusBikeCadencePcc.ICalculatedCadenceReceiver() { 66 | @Override 67 | public void onNewCalculatedCadence(long estTimestamp, EnumSet eventFlags, final BigDecimal calculatedCadence) { 68 | controller.setCadence(calculatedCadence.doubleValue()); 69 | } 70 | } 71 | ); 72 | } 73 | } 74 | }, new AntPluginPcc.IDeviceStateChangeReceiver() { 75 | @Override 76 | public void onDeviceStateChange(DeviceState deviceState) { 77 | if (deviceState.equals(DeviceState.DEAD)) { 78 | //zeroReadings(); 79 | } 80 | } 81 | }); 82 | } 83 | 84 | 85 | } else { 86 | if (resultCode == com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult.SEARCH_TIMEOUT) { 87 | requestAccess(); 88 | } 89 | } 90 | } 91 | }; 92 | 93 | 94 | requestAccess(); 95 | } 96 | 97 | @Override 98 | protected void requestAccess() { 99 | releaseHandle = AntPlusBikeSpeedDistancePcc.requestAccess(TrainApplication.getAppContext(), deviceNumber, 0, true, mResultReceiver, mDeviceStateChangeReceiver); 100 | } 101 | 102 | @Override 103 | protected void zeroReadings() { 104 | controller.setCadence(0); 105 | controller.setSpeed(0); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/train/DeviceConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.train; 2 | 3 | import com.google.firebase.database.Exclude; 4 | import com.trainerdb.trainerandroid.data.RealtimeController; 5 | 6 | /** 7 | * Created by Daniel on 23/06/2016. 8 | */ 9 | public class DeviceConfiguration { 10 | private DeviceType type; 11 | private String name; 12 | //private int wheelSize; 13 | //private int postProcess; 14 | private RealtimeController controller; 15 | 16 | @Exclude 17 | public DeviceType getTypeEnum() { 18 | return type; 19 | } 20 | 21 | @Exclude 22 | public void setTypeEnum(DeviceType type) { 23 | this.type = type; 24 | } 25 | 26 | public String getType() { 27 | if (type == null) { 28 | return null; 29 | } else { 30 | return type.name(); 31 | } 32 | } 33 | 34 | public void setType(String typeString) { 35 | // Get enum from string 36 | if (typeString == null) { 37 | this.type = null; 38 | } else { 39 | this.type = DeviceType.valueOf(typeString); 40 | } 41 | } 42 | 43 | public String getName() { 44 | return name; 45 | } 46 | 47 | public void setName(String name) { 48 | this.name = name; 49 | } 50 | /* 51 | public int getWheelSize() { 52 | return wheelSize; 53 | } 54 | 55 | public void setWheelSize(int wheelSize) { 56 | this.wheelSize = wheelSize; 57 | } 58 | 59 | public int getPostProcess() { 60 | return postProcess; 61 | } 62 | 63 | public void setPostProcess(int postProcess) { 64 | this.postProcess = postProcess; 65 | }*/ 66 | 67 | @Exclude 68 | public RealtimeController getController() { 69 | return controller; 70 | } 71 | @Exclude 72 | public void setController(RealtimeController controller) { 73 | this.controller = controller; 74 | } 75 | 76 | public enum DeviceType { 77 | DEV_NULL, DEV_ANTLOCAL 78 | } 79 | 80 | public DeviceConfiguration() { 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/train/DeviceConfigurations.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.train; 2 | 3 | import com.trainerdb.trainerandroid.TrainApplication; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Created by Daniel on 23/06/2016. 10 | */ 11 | public class DeviceConfigurations { 12 | 13 | private static List readConfig() { 14 | List entries = new ArrayList<>(); 15 | DeviceConfiguration c = new DeviceConfiguration(); 16 | c.setName("DEVICE"); 17 | c.setTypeEnum(TrainApplication.getDeviceType()); 18 | entries.add(c); 19 | 20 | return entries; 21 | } 22 | 23 | public static List getList(){ 24 | return readConfig(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/train/LapControl.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.train; 2 | 3 | import com.trainerdb.trainerandroid.TrainApplication; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * Created by Daniel on 30/06/2016. 8 | */ 9 | 10 | public class LapControl implements Serializable { 11 | private int count; 12 | private double sumHr, sumWatts, sumCadence, sumSpeed; 13 | private double wattsMin, wattsMax, hrMin, hrMax, cadenceMin, cadenceMax, speedMin, speedMax; 14 | private double distanceStart, distanceEnd; 15 | private int lapNum; 16 | private String name; 17 | private long start, end; 18 | private boolean isEmptyDistance; 19 | private NormalizedPower npLap; 20 | private int ftp = TrainApplication.getFTP(); 21 | private int recPerSec; 22 | 23 | public LapControl() { 24 | clear(); 25 | } 26 | 27 | public LapControl(int recPerSec, int lapNum, String name, long start, long end) { 28 | super(); 29 | this.lapNum = lapNum; 30 | this.name = name; 31 | this.start = start; 32 | this.end = end; 33 | this.recPerSec = recPerSec; 34 | npLap = new NormalizedPower(ftp, recPerSec); 35 | } 36 | 37 | public void clear() { 38 | count = lapNum = 0; 39 | start = end = 0; 40 | name = ""; 41 | sumHr = sumWatts = sumCadence = sumSpeed = 0; 42 | wattsMax = hrMax = cadenceMax = speedMax = 0; 43 | distanceEnd = distanceStart = 0; 44 | cadenceMin = Double.MAX_VALUE; 45 | speedMin = Double.MAX_VALUE; 46 | wattsMin = Double.MAX_VALUE; 47 | hrMin = Double.MAX_VALUE; 48 | isEmptyDistance = true; 49 | } 50 | 51 | public void add() { 52 | count++; 53 | } 54 | 55 | public void setHr(double value) { 56 | sumHr += value; 57 | if (value > hrMax) hrMax = value; 58 | else if (value < hrMin) hrMin = value; 59 | } 60 | 61 | public void setWatts(double value) { 62 | sumWatts += value; 63 | if (value > wattsMax) wattsMax = value; 64 | else if (value < wattsMin) wattsMin = value; 65 | 66 | this.npLap.set(value); 67 | } 68 | 69 | public void setCadence(double value) { 70 | sumCadence += value; 71 | if (value > cadenceMax) cadenceMax = value; 72 | else if (value < cadenceMin) cadenceMin = value; 73 | } 74 | 75 | public void setSpeed(double value) { 76 | sumSpeed += value; 77 | if (value > speedMax) speedMax = value; 78 | else if (value < speedMin) speedMin = value; 79 | } 80 | 81 | public double getHr() { 82 | return sumHr / count; 83 | } 84 | 85 | public double getCadence() { 86 | return sumCadence / count; 87 | } 88 | 89 | public double getSpeed() { 90 | return sumSpeed / count; 91 | } 92 | 93 | public double getWatts() { 94 | return sumWatts / count; 95 | } 96 | 97 | public double getHrMin() { 98 | return hrMin; 99 | } 100 | 101 | public double getHrMax() { 102 | return hrMax; 103 | } 104 | 105 | public double getCadenceMax() { 106 | return cadenceMax; 107 | } 108 | 109 | public double getCadenceMin() { 110 | return cadenceMin; 111 | } 112 | 113 | public double getSpeedMax() { 114 | return speedMax; 115 | } 116 | 117 | public double getSpeedMin() { 118 | return speedMin; 119 | } 120 | 121 | public double getWattsMax() { 122 | return wattsMax; 123 | } 124 | 125 | public double getWattsMin() { 126 | return wattsMin; 127 | } 128 | 129 | public void setDistance(double distance) { 130 | if (isEmptyDistance) { 131 | distanceStart = distance; 132 | isEmptyDistance = false; 133 | } 134 | distanceEnd = distance; 135 | } 136 | 137 | public int getCount() { 138 | return count; 139 | } 140 | 141 | public double getDistance() { 142 | return distanceEnd - distanceStart; 143 | } 144 | 145 | public boolean isRunning(long now) { 146 | return start <= now && end >= now ? true : false; 147 | } 148 | 149 | public long getEnd() { 150 | return end; 151 | } 152 | 153 | public void setEnd(long end) { 154 | this.end = end; 155 | } 156 | 157 | public int getLapNum() { 158 | return lapNum; 159 | } 160 | 161 | public void setLapNum(int lapNum) { 162 | this.lapNum = lapNum; 163 | } 164 | 165 | public String getName() { 166 | return name; 167 | } 168 | 169 | public void setName(String name) { 170 | this.name = name; 171 | } 172 | 173 | public long getStart() { 174 | return start; 175 | } 176 | 177 | public void setStart(long start) { 178 | this.start = start; 179 | } 180 | 181 | public NormalizedPower getNp() { 182 | return npLap; 183 | } 184 | 185 | public double getKj() { 186 | return sumWatts / (recPerSec * 1000); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/train/NormalizedPower.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.train; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by dcotrim on 12/07/2016. 7 | */ 8 | public class NormalizedPower implements Serializable { 9 | Rolling watts30; 10 | private int count = 0; 11 | private double rollingSum, ftp = 0; 12 | private int recPerSec; 13 | 14 | public NormalizedPower(double ftp, int recPerSec) { 15 | this.recPerSec = recPerSec; 16 | this.ftp = ftp; 17 | watts30 = new Rolling(recPerSec * 30); 18 | } 19 | 20 | public void clear() { 21 | watts30.clear(); 22 | count = 0; 23 | rollingSum = 0; 24 | } 25 | 26 | public void set(double watts) { 27 | watts30.add(watts); 28 | count++; 29 | rollingSum += Math.pow(watts30.getAverage(), 4); 30 | } 31 | 32 | public double getNp() { 33 | if (count == 0) 34 | return 0; 35 | return Math.pow(rollingSum / count, 0.25); 36 | } 37 | 38 | public double getIntensityFactor() { 39 | return getNp() / ftp; 40 | } 41 | 42 | public double getTss() { 43 | double normWork = getNp() * (count / recPerSec); 44 | double rawTSS = normWork * getIntensityFactor(); 45 | double workInAnHourAtCP = ftp * 3600; 46 | return rawTSS / workInAnHourAtCP * 100.0; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/train/NullController.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.train; 2 | 3 | import com.trainerdb.trainerandroid.data.RealtimeController; 4 | 5 | /** 6 | * Created by dcotrim on 22/06/2016. 7 | */ 8 | public class NullController extends RealtimeController { 9 | private int count = 0; 10 | double load; 11 | 12 | public NullController(DeviceConfiguration device){ 13 | super(device); 14 | } 15 | 16 | @Override 17 | public void getRealtimeData(RealtimeData rtData) { 18 | rtData.setName("Null"); 19 | rtData.setWatts(load + randomWithRange(0, 10) - 5); 20 | rtData.setSpeed(25 + randomWithRange(0, 4) - 2); 21 | rtData.setCadence(85 + randomWithRange(0, 9) - 5); 22 | rtData.setHr(145 + randomWithRange(0, 2) - 2); 23 | rtData.setLoad(load); 24 | //processRealTimeData(rtData); 25 | 26 | count++; 27 | } 28 | 29 | @Override 30 | public void setLoad(double load) { 31 | this.load = load; 32 | } 33 | 34 | @Override 35 | public void start() { 36 | 37 | } 38 | 39 | @Override 40 | public void stop() { 41 | 42 | } 43 | 44 | int randomWithRange(int min, int max) { 45 | int range = (max - min) + 1; 46 | return (int) (Math.random() * range) + min; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/train/RealtimeData.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.train; 2 | 3 | /** 4 | * Created by dcotrim on 22/06/2016. 5 | */ 6 | public class RealtimeData { 7 | private String name; 8 | private double hr; 9 | private double watts; 10 | private double speed; 11 | private double cadence; 12 | private double distance; 13 | private long msecs; 14 | private long lapMsecs; 15 | private long lapMsecsRemaining; 16 | private double load; 17 | private String deviceStatus; 18 | private int lap; 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | public double getHr() { 29 | return hr; 30 | } 31 | 32 | public void setHr(double hr) { 33 | this.hr = hr; 34 | } 35 | 36 | public double getWatts() { 37 | return watts; 38 | } 39 | 40 | public void setWatts(double watts) { 41 | this.watts = watts; 42 | } 43 | 44 | public double getSpeed() { 45 | return speed; 46 | } 47 | 48 | public void setSpeed(double speed) { 49 | this.speed = speed; 50 | } 51 | 52 | public double getCadence() { 53 | return cadence; 54 | } 55 | 56 | public void setCadence(double cadence) { 57 | this.cadence = cadence; 58 | } 59 | 60 | public double getDistance() { 61 | return distance; 62 | } 63 | 64 | public void setDistance(double distance) { 65 | this.distance = distance; 66 | } 67 | 68 | public long getMsecs() { 69 | return msecs; 70 | } 71 | 72 | public void setMsecs(long msecs) { 73 | this.msecs = msecs; 74 | } 75 | 76 | public long getLapMsecs() { 77 | return lapMsecs; 78 | } 79 | 80 | public void setLapMsecs(long lapMsecs) { 81 | this.lapMsecs = lapMsecs; 82 | } 83 | 84 | public long getLapMsecsRemaining() { 85 | return lapMsecsRemaining; 86 | } 87 | 88 | public void setLapMsecsRemaining(long lapMsecsRemaining) { 89 | this.lapMsecsRemaining = lapMsecsRemaining; 90 | } 91 | 92 | public double getLoad() { 93 | return load; 94 | } 95 | 96 | public void setLoad(double load) { 97 | this.load = load; 98 | } 99 | 100 | public String getDeviceStatus() { 101 | return deviceStatus; 102 | } 103 | 104 | public void setDeviceStatus(String deviceStatus) { 105 | this.deviceStatus = deviceStatus; 106 | } 107 | 108 | public int getLap() { 109 | return lap; 110 | } 111 | 112 | public void setLap(int lap) { 113 | this.lap = lap; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/train/Rolling.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.train; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * Created by Daniel on 30/06/2016. 10 | */ 11 | public class Rolling implements Serializable { 12 | 13 | private int size; 14 | private double total = 0d; 15 | private int index = 0; 16 | private double samples[]; 17 | 18 | public Rolling(int size) { 19 | this.size = size; 20 | samples = new double[size]; 21 | for (int i = 0; i < size; i++) samples[i] = 0d; 22 | } 23 | 24 | public void clear() { 25 | total = 0; 26 | for (int i = 0; i < size; i++) samples[i] = 0d; 27 | } 28 | 29 | public void add(double x) { 30 | total -= samples[index]; 31 | samples[index] = x; 32 | total += x; 33 | if (++index == size) index = 0; // cheaper than modulus 34 | } 35 | 36 | public double getAverage() { 37 | return total / size; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/train/StopWatch.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.train; 2 | 3 | import com.trainerdb.trainerandroid.TrainApplication; 4 | 5 | /** 6 | * Created by Daniel on 23/06/2016. 7 | */ 8 | public class StopWatch { 9 | 10 | /** 11 | * The start time. 12 | */ 13 | private long startTime = -1; 14 | /** 15 | * The stop time. 16 | */ 17 | private long stopTime = -1; 18 | 19 | /** 20 | *

Constructor.

21 | */ 22 | public StopWatch() { 23 | } 24 | 25 | /** 26 | *

Start the stopwatch.

27 | * 28 | *

This method starts a new timing session, clearing any previous values.

29 | */ 30 | public void start() { 31 | stopTime = -1; 32 | startTime = System.currentTimeMillis(); 33 | } 34 | 35 | 36 | public long restart() { 37 | long elapsed = elapsed(); 38 | start(); 39 | return elapsed; 40 | } 41 | 42 | /** 43 | *

Stop the stopwatch.

44 | * 45 | *

This method ends a new timing session, allowing the time to be retrieved.

46 | */ 47 | public void stop() { 48 | stopTime = System.currentTimeMillis(); 49 | } 50 | 51 | /** 52 | *

Reset the stopwatch.

53 | * 54 | *

This method clears the internal values to allow the object to be reused.

55 | */ 56 | public void reset() { 57 | startTime = -1; 58 | stopTime = -1; 59 | } 60 | 61 | /** 62 | *

Split the time.

63 | * 64 | *

This method sets the stop time of the watch to allow a time to be extracted. 65 | * The start time is unaffected, enabling {@link #unsplit()} to contine the 66 | * timing from the original start point.

67 | */ 68 | public void split() { 69 | stopTime = System.currentTimeMillis(); 70 | } 71 | 72 | /** 73 | *

Remove a split.

74 | * 75 | *

This method clears the stop time. The start time is unaffected, enabling 76 | * timing from the original start point to continue.

77 | */ 78 | public void unsplit() { 79 | stopTime = -1; 80 | } 81 | 82 | /** 83 | *

Suspend the stopwatch for later resumption.

84 | * 85 | *

This method suspends the watch until it is resumed. The watch will not include 86 | * time between the suspend and resume calls in the total time.

87 | */ 88 | public void suspend() { 89 | stopTime = System.currentTimeMillis(); 90 | } 91 | 92 | /** 93 | *

Resume the stopwatch after a suspend.

94 | * 95 | *

This method resumes the watch after it was suspended. The watch will not include 96 | * time between the suspend and resume calls in the total time.

97 | */ 98 | public void resume() { 99 | startTime += (System.currentTimeMillis() - stopTime); 100 | stopTime = -1; 101 | } 102 | 103 | /** 104 | *

Get the time on the stopwatch.

105 | * 106 | *

This is either the time between start and latest split, between start 107 | * and stop, or the time between the start and the moment this method is called.

108 | * 109 | * @return the time in milliseconds 110 | */ 111 | public long elapsed() { 112 | if (stopTime == -1) { 113 | if (startTime == -1) { 114 | return 0; 115 | } 116 | return (System.currentTimeMillis() - this.startTime); 117 | } 118 | return (this.stopTime - this.startTime); 119 | } 120 | 121 | /** 122 | *

Gets a summary of the time that the stopwatch recorded as a string.

123 | * 124 | *

The format used is ISO8601-like, 125 | * hours:minutes:seconds.milliseconds.

126 | * 127 | * @return the time as a String 128 | */ 129 | public String toString() { 130 | return TrainApplication.formatTime(elapsed()); 131 | } 132 | 133 | 134 | 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/train/TelemetryEnum.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.train; 2 | 3 | /** 4 | * Created by Daniel on 07/07/2016. 5 | */ 6 | public enum TelemetryEnum { 7 | POWER, 8 | CADENCE, 9 | HR, 10 | SPEED, 11 | 12 | TARGET, 13 | KM, 14 | KJ, 15 | 16 | AVG_POWER, 17 | AVG_SPEED, 18 | AVG_CAD, 19 | AVG_HR, 20 | 21 | MAX_POWER, 22 | MAX_SPEED, 23 | MAX_CAD, 24 | MAX_HR, 25 | 26 | LAP, 27 | LP_POWER, 28 | LP_SPEED, 29 | LP_CAD, 30 | LP_HR, 31 | 32 | LP_NP, 33 | LP_TSS, 34 | LP_IF, 35 | LP_KM, 36 | LP_KJ, 37 | 38 | POWER_30S, 39 | 40 | NP, 41 | TSS, 42 | IF, 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/train/TelemetryListener.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.train; 2 | 3 | 4 | import com.trainerdb.trainerandroid.data.WorkoutData; 5 | 6 | import java.io.File; 7 | 8 | /** 9 | * Created by Daniel on 23/06/2016. 10 | */ 11 | public interface TelemetryListener { 12 | void telemetryUpdate(RealtimeData rtData); 13 | void setNow(long msecs); 14 | void lapComplete(long now, int lapNum); 15 | void newText(long now, String text); 16 | void workoutComplete(WorkoutData workout, File recordFile, long startTime, long totalTicks); 17 | void telemetryStart(WorkoutData workout, File recordFile, long startTime); 18 | void telemetryStop(); 19 | void telemetryResume(); 20 | void telemetryPause(); 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/train/TelemetryView.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.train; 2 | 3 | import android.content.Context; 4 | import android.support.v4.content.ContextCompat; 5 | import android.util.AttributeSet; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.LinearLayout; 10 | import android.widget.TextView; 11 | 12 | import com.trainerdb.trainerandroid.AutoResizeTextView; 13 | import com.trainerdb.trainerandroid.R; 14 | 15 | /** 16 | * Created by Daniel on 07/07/2016. 17 | */ 18 | public class TelemetryView extends LinearLayout { 19 | private TextView header; 20 | private AutoResizeTextView data; 21 | private TelemetryEnum type; 22 | 23 | public TelemetryView(Context context, AttributeSet attrs) { 24 | super(context, attrs); 25 | 26 | LayoutInflater inflater = (LayoutInflater) context 27 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 28 | 29 | inflater.inflate(R.layout.view_telemetry, this, true); 30 | 31 | header = (TextView) getChildAt(0); 32 | 33 | data = (AutoResizeTextView) getChildAt(1); 34 | } 35 | 36 | public TelemetryView(Context context) { 37 | this(context, null); 38 | } 39 | 40 | public void setType(TelemetryEnum type) { 41 | this.type = type; 42 | this.header.setText(type.toString()); 43 | 44 | switch (type) { 45 | case CADENCE: 46 | data.setTextColor(ContextCompat.getColor(this.getContext(), R.color.trainCadence)); 47 | break; 48 | case LP_CAD: 49 | data.setTextColor(ContextCompat.getColor(this.getContext(), R.color.trainCadence)); 50 | break; 51 | case KM: 52 | data.setTextColor(ContextCompat.getColor(this.getContext(), R.color.trainDistance)); 53 | break; 54 | case HR: 55 | data.setTextColor(ContextCompat.getColor(this.getContext(), R.color.trainHearRate)); 56 | break; 57 | case IF: 58 | data.setTextColor(ContextCompat.getColor(this.getContext(), R.color.trainPower)); 59 | break; 60 | case LAP: 61 | data.setTextColor(ContextCompat.getColor(this.getContext(), R.color.trainLap)); 62 | break; 63 | case LP_HR: 64 | data.setTextColor(ContextCompat.getColor(this.getContext(), R.color.trainLapHr)); 65 | break; 66 | case LP_POWER: 67 | data.setTextColor(ContextCompat.getColor(this.getContext(), R.color.trainLapPower)); 68 | break; 69 | case NP: 70 | data.setTextColor(ContextCompat.getColor(this.getContext(), R.color.trainPower)); 71 | break; 72 | case POWER: 73 | data.setTextColor(ContextCompat.getColor(this.getContext(), R.color.trainPower)); 74 | break; 75 | case POWER_30S: 76 | data.setTextColor(ContextCompat.getColor(this.getContext(), R.color.trainPower)); 77 | break; 78 | case SPEED: 79 | data.setTextColor(ContextCompat.getColor(this.getContext(), R.color.trainSpeed)); 80 | break; 81 | case TARGET: 82 | data.setTextColor(ContextCompat.getColor(this.getContext(), R.color.trainTarget)); 83 | break; 84 | case TSS: 85 | data.setTextColor(ContextCompat.getColor(this.getContext(), R.color.trainPower)); 86 | break; 87 | } 88 | } 89 | 90 | public void setValue(String text) { 91 | this.data.setText(text); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/train/TrainFile.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.train; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.support.v4.content.FileProvider; 8 | import java.io.File; 9 | 10 | /** 11 | * Created by dcotrim on 29/06/2016. 12 | */ 13 | public class TrainFile { 14 | private File file; 15 | private boolean selected = false; 16 | 17 | public TrainFile(File file) { 18 | this.file = file; 19 | } 20 | 21 | public File getFile() { 22 | return file; 23 | } 24 | 25 | public void setFile(File file) { 26 | this.file = file; 27 | } 28 | 29 | public void setSelected(boolean selected) { 30 | this.selected = selected; 31 | } 32 | 33 | public boolean isSelected() { 34 | return selected; 35 | } 36 | 37 | public String getName() { 38 | return file.getName(); 39 | } 40 | 41 | public void shareFile(Activity context) { 42 | Intent shareIntent = new Intent(); 43 | shareIntent.setAction(Intent.ACTION_SEND); 44 | shareIntent.setType("application/xml"); 45 | 46 | Uri fileUri = FileProvider.getUriForFile(context, "com.trainerandroid.fileprovider", file); 47 | shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 48 | shareIntent.setDataAndType(fileUri, context.getContentResolver().getType(fileUri)); 49 | 50 | // Set the result 51 | context.setResult(Activity.RESULT_OK, shareIntent); 52 | 53 | shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri); 54 | context.startActivity(Intent.createChooser(shareIntent, "Share")); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/trainerdb/trainerandroid/train/TrainState.java: -------------------------------------------------------------------------------- 1 | package com.trainerdb.trainerandroid.train; 2 | 3 | import android.net.Uri; 4 | import android.support.v4.content.FileProvider; 5 | import com.trainerdb.trainerandroid.TrainApplication; 6 | import com.trainerdb.trainerandroid.data.LapPoint; 7 | import com.trainerdb.trainerandroid.data.WorkoutData; 8 | 9 | import java.io.File; 10 | import java.io.Serializable; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * Created by Daniel on 11/07/2016. 16 | */ 17 | public class TrainState implements Serializable { 18 | public WorkoutData workout; 19 | //public WorkoutRecord record; 20 | 21 | public double wattsSum, cadenceSum, speedSum, hrSum; 22 | public double distance; 23 | public double ftp = TrainApplication.getFTP(); 24 | public int hertz, count; 25 | 26 | public int hrMax; 27 | public int speedMax; 28 | public int cadenceMax; 29 | 30 | public int lapBeep, lap; 31 | public boolean recording = false, cancelCalled = false; 32 | public boolean isNewLap = false; 33 | public long now = 0; 34 | 35 | public List lapsControl= new ArrayList<>(); 36 | 37 | public ArrayList watts = new ArrayList<>(); // 1s samples [watts] 38 | public ArrayList hr = new ArrayList<>(); // 1s samples [bpm] 39 | public ArrayList speed = new ArrayList<>(); // 1s samples [km/h] 40 | public ArrayList cadence = new ArrayList<>(); // 1s samples [rpm] 41 | 42 | public Rolling watts30; 43 | private int recPerSec; 44 | 45 | public TrainState(WorkoutData workout, int recPerSec) { 46 | this.recPerSec = recPerSec; 47 | this.workout = workout; 48 | watts30 = new Rolling(recPerSec * 30); 49 | clear(); 50 | } 51 | 52 | public void clear() { 53 | hertz = count = 0; 54 | wattsSum = hrSum = speedSum = cadenceSum = 0; 55 | distance = 0; 56 | 57 | // set initial 58 | cadenceMax = 600; 59 | hrMax = 220; 60 | speedMax = 50; 61 | lapBeep = 0; 62 | 63 | watts30.clear(); 64 | watts.clear(); 65 | hr.clear(); 66 | speed.clear(); 67 | cadence.clear(); 68 | 69 | lapsControl.clear(); 70 | 71 | for (LapPoint lap : workout.getErg().getLapPoints()) { 72 | lapsControl.add(new LapControl(recPerSec, lap.lapNum, lap.name, lap.x, lap.y)); 73 | } 74 | } 75 | 76 | private void newLapInfo(long now, double hr, double cad, double watts, double speed, double distance) { 77 | for (LapControl lap : this.lapsControl) { 78 | if (lap.isRunning(now)) { 79 | lap.add(); 80 | lap.setCadence(cad); 81 | lap.setHr(hr); 82 | lap.setWatts(watts); 83 | lap.setSpeed(speed); 84 | lap.setDistance(distance); 85 | } 86 | } 87 | } 88 | 89 | public LapControl getMainLap() { 90 | return lapsControl.get(0); 91 | } 92 | 93 | public LapControl getCurrentLap() { 94 | return this.lapsControl.get(lap); 95 | } 96 | 97 | public void workoutStart(File recordFile, long startTime) { 98 | } 99 | 100 | private void createInterval(LapControl avgLap) { 101 | } 102 | 103 | public void workoutComplete(File recordFile, long totalTicks, 104 | byte[] bitmap, byte[] bitmapMin) { 105 | LapControl workoutLap = this.lapsControl.get(0); 106 | 107 | for (LapControl lap : this.lapsControl) { 108 | if (lap.getCount() > 0) 109 | createInterval(lap); 110 | } 111 | 112 | Uri fileUri = FileProvider.getUriForFile(TrainApplication.getAppContext(), 113 | "com.trainerandroid.fileprovider", recordFile); 114 | 115 | } 116 | 117 | public void updateRealtimeData(RealtimeData rt) { 118 | count++; 119 | now = rt.getMsecs(); 120 | distance = rt.getDistance(); 121 | lap = rt.getLap(); 122 | 123 | if (isNewLap) isNewLap = false; 124 | 125 | this.newLapInfo(now, rt.getHr(), rt.getCadence(), rt.getWatts(), 126 | rt.getSpeed(), rt.getDistance()); 127 | 128 | watts30.add(rt.getWatts()); 129 | 130 | //wbalSum += rt.getWbal(); 131 | wattsSum += rt.getWatts(); 132 | hrSum += rt.getHr(); 133 | cadenceSum += rt.getCadence(); 134 | speedSum += rt.getSpeed(); 135 | 136 | hertz++; 137 | 138 | // did we get 5 samples (5hz refresh rate) ? 139 | if (hertz == recPerSec) { 140 | //int b = wbalSum / 5.0f; 141 | //wbal << b; 142 | int w = (int) (wattsSum / recPerSec); 143 | watts.add(w); 144 | int h = (int) (hrSum / recPerSec); 145 | hr.add(h); 146 | double s = (speedSum / recPerSec); 147 | speed.add(s); 148 | int c = (int) (cadenceSum / recPerSec); 149 | cadence.add(c); 150 | 151 | // clear for next time 152 | hertz = 0; 153 | wattsSum = hrSum = speedSum = cadenceSum = 0; 154 | } 155 | } 156 | 157 | 158 | } 159 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-hdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_notifications_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-hdpi/ic_notifications_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-hdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-mdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_notifications_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-mdpi/ic_notifications_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-mdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_info_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_camera.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_gallery.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_manage.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_send.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_slideshow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_notifications_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_sync_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-xhdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_notifications_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-xhdpi/ic_notifications_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-xhdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-xxhdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_notifications_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-xxhdpi/ic_notifications_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-xxhdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-xxxhdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_notifications_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-xxxhdpi/ic_notifications_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/drawable-xxxhdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout-land/view_telemetry.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout-w900dp/workout_list.xml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | 20 | 31 | 32 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_train_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_train_file_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 21 | 22 | 23 | 24 | 29 | 30 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_workout_detail.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | 17 | 28 | 29 | 37 | 38 | 45 | 46 | 47 | 48 | 49 | 50 | 55 | 56 | 57 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_workout_filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 21 | 22 | 23 | 24 | 30 | 31 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_workout_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_bar_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/nav_header_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 20 | 21 | 27 | 28 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/train_file_list_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_telemetry.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/workout_detail.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 24 | 25 | 29 | 30 | 35 | 36 | 45 | 46 | 56 | 57 | 58 | 63 | 64 | 73 | 74 | 84 | 85 | 86 | 91 | 92 | 101 | 102 | 112 | 113 | 114 | 115 | 116 | 125 | 126 | 134 | 135 | 144 | -------------------------------------------------------------------------------- /app/src/main/res/layout/workout_filter_group.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/workout_filter_list_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 17 | 18 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/workout_filter_sort_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 26 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/workout_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/workout_list_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 19 | 20 | 21 | 22 | 23 | 31 | 32 | 36 | 37 | 42 | 43 | 50 | 51 | 59 | 60 | 61 | 66 | 67 | 74 | 75 | 83 | 84 | 85 | 90 | 91 | 98 | 99 | 107 | 108 | 112 | 113 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_main_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_train_file_list.xml: -------------------------------------------------------------------------------- 1 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/toolbar_files.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/menu/toolbar_filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/menu/toolbar_train.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/toolbar_workout_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrainerDb/TrainerAndroid/9a3e8c4de16b3021148de00e9f4df872d3179133/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #FFFF00 8 | #FFFF00 9 | #FFFF00 10 | #FF0000 11 | #FF0000 12 | #FF0000 13 | #00ff00 14 | #9AF0FF 15 | #9AF0FF 16 | 17 | #FF00FF 18 | #2A00FF 19 | #00AAFF 20 | #00FF80 21 | #55FF00 22 | #FFD500 23 | #ff0000 24 | #555555 25 | #555555 26 | #555555 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 180dp 5 | 200dp 6 | 16dp 7 | 8 | 16dp 9 | 16dp 10 | 11 | 16dp 12 | 160dp 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/drawables.xml: -------------------------------------------------------------------------------- 1 | 2 | @android:drawable/ic_menu_camera 3 | @android:drawable/ic_menu_gallery 4 | @android:drawable/ic_menu_slideshow 5 | @android:drawable/ic_menu_manage 6 | @android:drawable/ic_menu_share 7 | @android:drawable/ic_menu_send 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 |