diff --git a/build.gradle b/build.gradle index 8823dc5fd..a367a5974 100644 --- a/build.gradle +++ b/build.gradle @@ -21,8 +21,8 @@ apply plugin: 'org.sonarqube' sonarqube { properties { - property "sonar.projectName", "Group 8 SOEN 6431" - property "sonar.projectKey", "Group-8-SOEN-6431" + property "sonar.projectName", "Group 11 SOEN 6431" + property "sonar.projectKey", "Group-11-SOEN-6431" property "sonar.host.url", "http://localhost:9000" property "sonar.login", "sqp_7f8adc6706b839b752be96c5d687384252602ab5" } @@ -83,7 +83,7 @@ android { defaultConfig { applicationId "de.dennisguse.opentracks" versionCode 5389 - versionName "v4.8.3" + versionName "v4.9.0" buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\"" diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 000000000..125d765cc Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/androidTest/java/de/dennisguse/opentracks/introduction/IntroductionActivityTest.java b/src/androidTest/java/de/dennisguse/opentracks/introduction/IntroductionActivityTest.java new file mode 100644 index 000000000..26059e934 --- /dev/null +++ b/src/androidTest/java/de/dennisguse/opentracks/introduction/IntroductionActivityTest.java @@ -0,0 +1,95 @@ + +import androidx.test.espresso.DataInteraction; +import androidx.test.espresso.ViewInteraction; +import androidx.test.filters.LargeTest; +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; + +import static androidx.test.InstrumentationRegistry.getInstrumentation; +import static androidx.test.espresso.Espresso.onData; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.Espresso.pressBack; +import static androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; +import static androidx.test.espresso.action.ViewActions.*; +import static androidx.test.espresso.assertion.ViewAssertions.*; +import static androidx.test.espresso.matcher.ViewMatchers.*; + +import de.dennisguse.opentracks.debug.R; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.hamcrest.core.IsInstanceOf; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.anything; +import static org.hamcrest.Matchers.is; + +@LargeTest +@RunWith(AndroidJUnit4.class) +public class IntroductionActivityTest { + + @Rule + public ActivityScenarioRule mActivityScenarioRule = + new ActivityScenarioRule<>(IntroductionActivity.class); + + @Test + public void introductionActivityTest() { + ViewInteraction floatingActionButton = onView( + allOf(withId(R.id.track_list_fab_action), withContentDescription("Record"), + childAtPosition( + childAtPosition( + withId(android.R.id.content), + 0), + 3), + isDisplayed())); + floatingActionButton.perform(click()); + + ViewInteraction textView = onView( + allOf(withId(android.R.id.text1), withText("Speed"), + withParent(allOf(IsInstanceOf.instanceOf(android.widget.ListView.class), + withParent(IsInstanceOf.instanceOf(android.widget.FrameLayout.class)))), + isDisplayed())); + textView.check(matches(withText("Speed"))); + + ViewInteraction textView2 = onView( + allOf(withId(android.R.id.text1), withText("Heart Rate"), + withParent(allOf(IsInstanceOf.instanceOf(android.widget.ListView.class), + withParent(IsInstanceOf.instanceOf(android.widget.FrameLayout.class)))), + isDisplayed())); + textView2.check(matches(withText("Heart Rate"))); + + ViewInteraction textView3 = onView( + allOf(withId(android.R.id.text1), withText("Distance"), + withParent(allOf(IsInstanceOf.instanceOf(android.widget.ListView.class), + withParent(IsInstanceOf.instanceOf(android.widget.FrameLayout.class)))), + isDisplayed())); + textView3.check(matches(withText("Distance"))); + } + + private static Matcher childAtPosition( + final Matcher parentMatcher, final int position) { + + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("Child at position " + position + " in parent "); + parentMatcher.describeTo(description); + } + + @Override + public boolean matchesSafely(View view) { + ViewParent parent = view.getParent(); + return parent instanceof ViewGroup && parentMatcher.matches(parent) + && view.equals(((ViewGroup) parent).getChildAt(position)); + } + }; + } +} diff --git a/src/main/.DS_Store b/src/main/.DS_Store new file mode 100644 index 000000000..eb93551af Binary files /dev/null and b/src/main/.DS_Store differ diff --git a/src/main/java/.DS_Store b/src/main/java/.DS_Store new file mode 100644 index 000000000..eaf3024af Binary files /dev/null and b/src/main/java/.DS_Store differ diff --git a/src/main/java/de/.DS_Store b/src/main/java/de/.DS_Store new file mode 100644 index 000000000..43ed06e61 Binary files /dev/null and b/src/main/java/de/.DS_Store differ diff --git a/src/main/java/de/dennisguse/.DS_Store b/src/main/java/de/dennisguse/.DS_Store new file mode 100644 index 000000000..cfe91c46e Binary files /dev/null and b/src/main/java/de/dennisguse/.DS_Store differ diff --git a/src/main/java/de/dennisguse/opentracks/.DS_Store b/src/main/java/de/dennisguse/opentracks/.DS_Store new file mode 100644 index 000000000..d78f26285 Binary files /dev/null and b/src/main/java/de/dennisguse/opentracks/.DS_Store differ diff --git a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java index bf10ba549..ddb649616 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java @@ -17,9 +17,11 @@ package de.dennisguse.opentracks; import android.app.ActivityOptions; +import android.app.AlertDialog; import android.app.SearchManager; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.database.Cursor; import android.graphics.drawable.AnimatedVectorDrawable; @@ -31,11 +33,13 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.widget.ImageView; import android.widget.Toast; - +import android.os.Handler; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.content.res.AppCompatResources; +import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.SearchView; import androidx.core.content.ContextCompat; import androidx.cursoradapter.widget.ResourceCursorAdapter; @@ -75,6 +79,8 @@ import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.PermissionRequester; import de.dennisguse.opentracks.util.StringUtils; +import android.os.CountDownTimer; +import android.view.MenuItem; /** * An activity displaying a list of tracks. @@ -82,9 +88,10 @@ * @author Leif Hendrik Wilden */ public class TrackListActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller { - + public static String notifChoice = "speed"; private static final String TAG = TrackListActivity.class.getSimpleName(); - + private CountDownTimer delayTimer; + private int selectedDelayInSeconds = 0; // The following are set in onCreate private TrackRecordingServiceConnection trackRecordingServiceConnection; private ResourceCursorAdapter resourceCursorAdapter; @@ -99,6 +106,14 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co private GpsStatusValue gpsStatusValue = TrackRecordingService.STATUS_GPS_DEFAULT; private RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT; + private static final String NOTIFICATION_PREFERENCE_KEY = "notification_preference_key"; + + // Constants for notification options + private static final int SPEED_METRIC_CHOICE = 0; + private static final int HEART_RATE_METRIC_CHOICE = 1; + private static final int DISTANCE_METRIC_CHOICE = 2; + private static final int DEFAULT_CHOICE = SPEED_METRIC_CHOICE; + // Callback when an item is selected in the contextual action mode private final ActivityUtils.ContextualActionModeCallback contextualActionModeCallback = new ActivityUtils.ContextualActionModeCallback() { @@ -107,7 +122,7 @@ public void onPrepare(Menu menu, int[] positions, long[] trackIds, boolean showS boolean isSingleSelection = trackIds.length == 1; viewBinding.bottomAppBar.performHide(true); - viewBinding.trackListFabAction.setVisibility(View.INVISIBLE); + viewBinding.button.setVisibility(View.INVISIBLE); menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection); menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll); @@ -120,7 +135,7 @@ public boolean onClick(int itemId, int[] positions, long[] trackIds) { @Override public void onDestroy() { - viewBinding.trackListFabAction.setVisibility(View.VISIBLE); + viewBinding.button.setVisibility(View.VISIBLE); viewBinding.bottomAppBar.performShow(true); } }; @@ -180,6 +195,24 @@ protected void onCreate(Bundle savedInstanceState) { } }); + MaterialButton timerButton = findViewById(R.id.timer_button); + + // Set up click listener for the timer button + timerButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // Show the dropdown menu + showTimerMenu(v); + } + }); + + + this.invalidateOptionsMenu(); + LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks); + + // Float button +// setFloatButton(); + viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView); viewBinding.trackList.setOnItemClickListener((parent, view, position, trackIdId) -> { Track.Id trackId = new Track.Id(trackIdId); @@ -238,25 +271,43 @@ public void bindView(View view, Context context, Cursor cursor) { viewBinding.trackList.setAdapter(resourceCursorAdapter); ActivityUtils.configureListViewContextualMenu(viewBinding.trackList, contextualActionModeCallback); - viewBinding.trackListFabAction.setOnClickListener((view) -> { + viewBinding.button.setOnClickListener((view) -> { if (recordingStatus.isRecording()) { Toast.makeText(TrackListActivity.this, getString(R.string.hold_to_stop), Toast.LENGTH_LONG).show(); return; } + // Not Recording -> Recording + try { + runOnUiThread(() -> { + for (int i = selectedDelayInSeconds; i >= 0; i--) { + + final int secondsLeft = i; + Toast toast = Toast.makeText(TrackListActivity.this,"Recording starts in " + secondsLeft + " seconds", Toast.LENGTH_SHORT); + toast.show(); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + toast.cancel(); + }}); + + } catch (Exception e) { + throw new RuntimeException(e); + } updateGpsMenuItem(false, true); new TrackRecordingServiceConnection((service, connection) -> { - Track.Id trackId = service.startNewTrack(); - + // Track.Id trackId = service.startNewTrack(); Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class); - newIntent.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId); + //newIntent.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId); startActivity(newIntent); connection.unbind(this); }).startAndBind(this, true); }); - viewBinding.trackListFabAction.setOnLongClickListener((view) -> { + viewBinding.button.setOnLongClickListener((view) -> { if (!recordingStatus.isRecording()) { return false; } @@ -265,13 +316,29 @@ public void bindView(View view, Context context, Cursor cursor) { ActivityUtils.vibrate(this, 1000); updateGpsMenuItem(false, false); trackRecordingServiceConnection.stopRecording(TrackListActivity.this); - viewBinding.trackListFabAction.setImageResource(R.drawable.ic_baseline_record_24); - viewBinding.trackListFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.red_dark)); + // viewBinding.button.setImageResource(R.drawable.ic_baseline_record_24); + viewBinding.button.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.red_dark)); + selectedDelayInSeconds=0; return true; }); setSupportActionBar(viewBinding.trackListToolbar); + if (recordingStatus.isRecording()) { + Toast.makeText(TrackListActivity.this, getString(R.string.hold_to_stop), Toast.LENGTH_LONG).show(); + return; + } + + // Not Recording -> Recording + updateGpsMenuItem(false, true); + new TrackRecordingServiceConnection((service, connection) -> { + //Track.Id trackId = service.startNewTrack(); + + Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class); +// newIntent.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId); + startActivity(newIntent); + connection.unbind(this); + }).startAndBind(this, true); loadData(getIntent()); } @@ -296,7 +363,7 @@ protected void onResume() { LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks); // Float button - setFloatButton(); +// setFloatButton(); } @Override @@ -312,8 +379,15 @@ protected void onDestroy() { super.onDestroy(); viewBinding = null; trackRecordingServiceConnection = null; + + // Cancel the delay timer if it's running + if (delayTimer != null) { + delayTimer.cancel(); + } } + + @Override protected View getRootView() { viewBinding = TrackListBinding.inflate(getLayoutInflater()); @@ -374,7 +448,76 @@ public boolean onKeyUp(int keyCode, KeyEvent event) { } return super.onKeyUp(keyCode, event); } + public void showTimerMenu(View view) { + PopupMenu popupMenu = new PopupMenu(this, view); + popupMenu.getMenuInflater().inflate(R.menu.timer_menu, popupMenu.getMenu()); + + popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + + + /*if (item.getItemId() == R.id.menu_no_timer) { + // Handle 0 seconds + showToast("Timer has been disabled"); + return true; + } + if (item.getItemId() == R.id.menu_1_second) { + // Handle 1-second selection + showToast("1 Second selected"); + return true; + } else if (item.getItemId() == R.id.menu_2_seconds) { + // Handle 2-seconds selection + showToast("2 Seconds selected"); + return true; + } + else if (item.getItemId() == R.id.menu_5_seconds) { + // Handle 5-seconds selection + showToast("5 Seconds selected"); + return true; + } + else if (item.getItemId() == R.id.menu_10_seconds) { + // Handle 10-seconds selection + showToast("10 Seconds selected"); + return true; + }else { + return false; + } + */ + handleDelaySelection(item.getItemId()); + return true; + } + }); + + popupMenu.show(); + } + private void handleDelaySelection(int itemId) { + if (itemId == R.id.menu_no_timer) { + // Handle no delay selection + showToast("Timer has been disabled"); + selectedDelayInSeconds = 0; + } else if (itemId == R.id.menu_1_second) { + selectedDelayInSeconds = 1; + showToast("1 Second selected"); + } else if (itemId == R.id.menu_2_seconds) { + selectedDelayInSeconds = 2; + showToast("2 Seconds selected"); + } else if (itemId == R.id.menu_5_seconds) { + selectedDelayInSeconds = 5; + showToast("5 Seconds selected"); + } else if (itemId == R.id.menu_10_seconds) { + selectedDelayInSeconds = 10; + showToast("10 Seconds selected"); + } + + } + + + + private void showToast(String message) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + } @Override public void overridePendingTransition(int enterAnim, int exitAnim) { //Disable animations as it is weird going into searchMode; looks okay for SplashScreen. @@ -445,6 +588,7 @@ private void updateGpsMenuItem(boolean isGpsStarted, boolean isRecording) { } } + /** * Handles a context item selection. * @@ -553,13 +697,84 @@ public void onGpsStatusChanged(GpsStatusValue newStatus) { updateGpsMenuItem(true, recordingStatus.isRecording()); } - private void setFloatButton() { - viewBinding.trackListFabAction.setImageResource(recordingStatus.isRecording() ? R.drawable.ic_baseline_stop_24 : R.drawable.ic_baseline_record_24); - viewBinding.trackListFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, recordingStatus.isRecording() ? R.color.opentracks : R.color.red_dark)); - } + // private void setFloatButton() { + // viewBinding.button.setImageResource(recordingStatus.isRecording() ? R.drawable.ic_baseline_stop_24 : R.drawable.ic_baseline_record_24); + // viewBinding.button.setBackgroundTintList(ContextCompat.getColorStateList(this, recordingStatus.isRecording() ? R.color.opentracks : R.color.red_dark)); + // } private void onRecordingStatusChanged(RecordingStatus status) { recordingStatus = status; - setFloatButton(); + // setFloatButton(); + } + + + // Add a new method for handling the start recording action + private void startRecording() { + // Not Recording -> Recording + try { + runOnUiThread(() -> { + for (int i = selectedDelayInSeconds; i >= 0; i--) { + + final int secondsLeft = i; + Toast toast = Toast.makeText(TrackListActivity.this,"Recording starts in " + secondsLeft + " seconds", Toast.LENGTH_SHORT); + toast.show(); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + toast.cancel(); + }}); + + } catch (Exception e) { + throw new RuntimeException(e); + } + updateGpsMenuItem(false, true); + new TrackRecordingServiceConnection((service, connection) -> { + Track.Id trackId = service.startNewTrack(); + + Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class); + newIntent.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId); + startActivity(newIntent); + + connection.unbind(this); + }).startAndBind(this, true); + } + + // Function to show the dialog for notification options + private void showNotificationOptionsDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.choose_notification_option) + .setItems(R.array.notification_options, (dialog, which) -> { + // Store the user's choice + saveNotificationPreference(which); + + // Update the notification based on the user's choice + updateNotification(); + + // Start recording after the user has made a choice + startRecording(); + }); + builder.create().show(); + } + + // Function to save the user's notification preference + private void saveNotificationPreference(int choice) { + SharedPreferences preferences = getPreferences(Context.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putInt(NOTIFICATION_PREFERENCE_KEY, choice); + editor.apply(); + } + + // Function to update the notification based on user's choice + private void updateNotification() { + SharedPreferences preferences = getPreferences(Context.MODE_PRIVATE); + int userChoice = preferences.getInt(NOTIFICATION_PREFERENCE_KEY, DEFAULT_CHOICE); + + switch (userChoice) { + case SPEED_METRIC_CHOICE -> notifChoice = "speed"; + case HEART_RATE_METRIC_CHOICE -> notifChoice = "heartRate"; + case DISTANCE_METRIC_CHOICE -> notifChoice = "distance"; + } } } diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java index d6e686008..754eebce3 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java @@ -132,10 +132,10 @@ protected void onCreate(Bundle savedInstanceState) { viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY)); } - viewBinding.trackRecordingFabAction.setImageResource(R.drawable.ic_baseline_stop_24); - viewBinding.trackRecordingFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.opentracks)); - viewBinding.trackRecordingFabAction.setBackgroundColor(ContextCompat.getColor(this, R.color.opentracks)); - viewBinding.trackRecordingFabAction.setOnLongClickListener((view) -> { +// viewBinding.holdToStopButton.setImageResource(R.drawable.ic_baseline_stop_24); +// viewBinding.holdToStopButton.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.opentracks)); +// viewBinding.holdToStopButton.setBackgroundColor(ContextCompat.getColor(this, R.color.opentracks)); + viewBinding.holdToStopButton.setOnLongClickListener((view) -> { ActivityUtils.vibrate(this, 1000); trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this); Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackStoppedActivity.class) @@ -145,7 +145,7 @@ protected void onCreate(Bundle savedInstanceState) { finish(); return true; }); - viewBinding.trackRecordingFabAction.setOnClickListener((view) -> Toast.makeText(TrackRecordingActivity.this, getString(R.string.hold_to_stop), Toast.LENGTH_LONG).show()); + viewBinding.holdToStopButton.setOnClickListener((view) -> Toast.makeText(TrackRecordingActivity.this, getString(R.string.hold_to_stop), Toast.LENGTH_LONG).show()); viewBinding.bottomAppBar.setNavigationIcon(R.drawable.ic_baseline_arrow_back_24); setSupportActionBar(viewBinding.bottomAppBar); diff --git a/src/main/java/de/dennisguse/opentracks/TrackStoppedActivity.java b/src/main/java/de/dennisguse/opentracks/TrackStoppedActivity.java index 03ddd5fc4..668f46996 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackStoppedActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackStoppedActivity.java @@ -5,6 +5,7 @@ import android.util.Log; import android.util.Pair; import android.view.View; +import android.widget.Button; import android.widget.ArrayAdapter; import de.dennisguse.opentracks.data.ContentProviderUtils; @@ -96,7 +97,10 @@ protected void onCreate(Bundle savedInstanceState) { finish(); }); - viewBinding.resumeButton.setOnClickListener(v -> { + + Button resumeButton = findViewById(R.id.resume_button); + resumeButton.setText("RESUME"); + resumeButton.setOnClickListener(v -> { storeTrackMetaData(contentProviderUtils, track); resumeTrackAndFinish(); }); diff --git a/src/main/java/de/dennisguse/opentracks/chart/ChartPoint.java b/src/main/java/de/dennisguse/opentracks/chart/ChartPoint.java index af44edb12..923513c42 100644 --- a/src/main/java/de/dennisguse/opentracks/chart/ChartPoint.java +++ b/src/main/java/de/dennisguse/opentracks/chart/ChartPoint.java @@ -21,12 +21,19 @@ public class ChartPoint { private Double cadence; private Double power; - @Deprecated + /** + * This class represents a chart point. + * + * @deprecated This constructor is deprecated and will be removed in a future version. + * Use an alternative constructor or method to create ChartPoint instances. + */ + @Deprecated(since = "1.0", forRemoval = true) @VisibleForTesting ChartPoint(double altitude) { this.altitude = altitude; } + public ChartPoint(@NonNull TrackStatistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, boolean chartByDistance, UnitSystem unitSystem) { if (chartByDistance) { timeOrDistance = trackStatistics.getTotalDistance().toKM_Miles(unitSystem); diff --git a/src/main/java/de/dennisguse/opentracks/data/models/WeatherInfo.java b/src/main/java/de/dennisguse/opentracks/data/models/WeatherInfo.java new file mode 100644 index 000000000..517951252 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/data/models/WeatherInfo.java @@ -0,0 +1,106 @@ +package de.dennisguse.opentracks.data.models; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public final class WeatherInfo implements Parcelable { + + private Track.Id id; + private double temperature; + private double windSpeed; + + private double humidity; + private String windDirection; + + public WeatherInfo(double temperature, double windSpeed, double humidity, String windDirection) { + this.temperature = temperature; + this.windSpeed = windSpeed; + this.humidity = humidity; + this.windDirection = windDirection; + } + + protected WeatherInfo(Parcel in) { + temperature = in.readDouble(); + windSpeed = in.readDouble(); + humidity = in.readDouble(); + windDirection = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public WeatherInfo createFromParcel(Parcel in) { + return new WeatherInfo(in); + } + + @Override + public WeatherInfo[] newArray(int size) { + return new WeatherInfo[size]; + } + }; + + @Nullable + public Track.Id getId() { + return id; + } + + public void setId(Track.Id id) { + this.id = id; + } + + public double getTemperature() { + return temperature; + } + + public void setTemperature(double temperature) { + this.temperature = temperature; + } + + public double getWindSpeed() { + return windSpeed; + } + + public void setWindSpeed(double windSpeed) { + this.windSpeed = windSpeed; + } + + public double getHumidity() { + return humidity; + } + + public void setHumidity(double humidity) { + this.humidity = humidity; + } + + public String getWindDirection() { + return windDirection; + } + + public void setWindDirection(String windDirection) { + this.windDirection = windDirection; + } + + @NonNull + @Override + public String toString() { + return "WeatherInfo{" + + "temperature=" + temperature + + ", windSpeed=" + windSpeed + + ", windDirection=" + windDirection + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeDouble(temperature); + parcel.writeDouble(windSpeed); + parcel.writeString(windDirection); + } +} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index 814109143..9036eaa86 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -119,7 +119,7 @@ public void onCreate() { @Override public void onDestroy() { Log.d(TAG, "Destroying"); - if (isRecording()) { + if (recordingStatus.isRecording()) { endCurrentTrack(); } diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceNotificationManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceNotificationManager.java index 7c9eb13ee..331d06597 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceNotificationManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceNotificationManager.java @@ -17,6 +17,7 @@ import de.dennisguse.opentracks.TrackListActivity; import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.DistanceFormatter; +import de.dennisguse.opentracks.data.models.HeartRate; import de.dennisguse.opentracks.data.models.SpeedFormatter; import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.settings.PreferencesUtils; @@ -93,9 +94,26 @@ void updateTrackPoint(Context context, TrackStatistics trackStatistics, TrackPoi previousLocationWasAccurate = currentLocationWasAccurate; } - notificationBuilder.setContentTitle(context.getString(R.string.track_distance_notification, formatter.formatDistance(trackStatistics.getTotalDistance()))); - String formattedSpeed = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(true).build(context).formatSpeed(trackPoint.getSpeed()); - notificationBuilder.setContentText(context.getString(R.string.track_speed_notification, formattedSpeed)); + switch (TrackListActivity.notifChoice) { + case "distance" -> { + notificationBuilder.setContentTitle(context.getString(R.string.track_distance_notification, formatter.formatDistance(trackStatistics.getTotalDistance()))); + } + case "speed" -> { + String formattedSpeed = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(true).build(context).formatSpeed(trackPoint.getSpeed()); + notificationBuilder.setContentTitle(context.getString(R.string.track_speed_notification, formattedSpeed)); + } + case "heartRate" -> { + if (trackPoint.hasHeartRate()) { + HeartRate heartRate = trackPoint.getHeartRate(); + String formattedHeartRate = String.valueOf(heartRate.getBPM()); + notificationBuilder.setContentTitle(context.getString(R.string.track_heart_rate_notification, formattedHeartRate)); + } else { + // Handle the case where no heart rate data is available + notificationBuilder.setContentTitle(context.getString(R.string.no_heart_rate_data)); + } + } + } + notificationBuilder.setSubText(context.getString(R.string.track_recording_notification_accuracy, formattedAccuracy)); updateNotification(); diff --git a/src/main/java/de/dennisguse/opentracks/services/WeatherFetchService.java b/src/main/java/de/dennisguse/opentracks/services/WeatherFetchService.java new file mode 100644 index 000000000..6a5165771 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/services/WeatherFetchService.java @@ -0,0 +1,96 @@ +package de.dennisguse.opentracks.services; + +import androidx.annotation.Nullable; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; + +import de.dennisguse.opentracks.data.models.WeatherInfo; + +public class WeatherFetchService { + public static final String API_KEY = "fa97a8d025bc2ed677edfd981a7491b7"; + public static final String API_URL = "http://api.weatherstack.com/current"; + + @Nullable + public static WeatherInfo fetchWeatherData(double latitudeDouble, double longitudeDouble) { + try { + + String latitude = String.valueOf(latitudeDouble); + String longitude = String.valueOf(longitudeDouble); + + URL url = getURL(latitude, longitude); + + HttpURLConnection connection = getHttpURLConnection(url); + StringBuilder result = getWeatherData(connection); + JSONObject current = getJsonConverter(result); + + // Extract weather information + double temperature = getTemperature(current); + double windSpeed = getWindSpeed(current); + double humidity = getHumidity(current); + String windDirection = getWindDirection(current); + + return new WeatherInfo(temperature, windSpeed, humidity, windDirection); + + } catch (IOException | JSONException e) { + e.printStackTrace(); + } + + return null; + } + + private static JSONObject getJsonConverter(StringBuilder result) throws JSONException { + JSONObject json = new JSONObject(result.toString()); + + return json.getJSONObject("current"); + } + + private static double getTemperature(JSONObject current) throws JSONException { + return current.getDouble("temperature"); + } + + private static double getHumidity(JSONObject current) throws JSONException { + return current.getDouble("humidity"); + } + + private static String getWindDirection(JSONObject current) throws JSONException { + return current.getString("wind_dir"); + } + + private static double getWindSpeed(JSONObject current) throws JSONException { + return current.getDouble("wind_speed"); + } + + private static StringBuilder getWeatherData(HttpURLConnection connection) throws IOException { + InputStream inputStream = connection.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder result = new StringBuilder(); + String line; + + while ((line = reader.readLine()) != null) { + result.append(line); + } + + return result; + } + + private static HttpURLConnection getHttpURLConnection(URL url) throws IOException { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + + return connection; + } + + private static URL getURL(String latitude, String longitude) throws MalformedURLException { + return new URL(API_URL + "?access_key=" + API_KEY + + "&query=" + latitude + "," + longitude); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncement.java b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncement.java index cc00f33aa..373a1bd23 100644 --- a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncement.java +++ b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncement.java @@ -312,9 +312,12 @@ public void announce(@NonNull Track track) { sensorStatistics = contentProviderUtils.getSensorStats(track.getId()); } - Spannable announcement = VoiceAnnouncementUtils.getAnnouncement(context, track.getTrackStatistics(), - PreferencesUtils.getUnitSystem(), PreferencesUtils.isReportSpeed(track), lastInterval, - sensorStatistics); + // Spannable announcement = VoiceAnnouncementUtils.getAnnouncement(context, + // track.getTrackStatistics(), PreferencesUtils.getUnitSystem(), + // PreferencesUtils.isReportSpeed(track), lastInterval, sensorStatistics); + // SpannableStringBuilder announcement = new SpannableStringBuilder(); + Spannable announcement = VoiceAnnouncementUtils.getMotivationalAnnouncements(); + // announcement.append("good job"); if (announcement.length() > 0) { // We don't care about the utterance id. It is supplied here to force diff --git a/src/main/java/de/dennisguse/opentracks/settings/AggregatedStatsSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/AggregatedStatsSettingsFragment.java new file mode 100644 index 000000000..ede8f270e --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/settings/AggregatedStatsSettingsFragment.java @@ -0,0 +1,23 @@ +package de.dennisguse.opentracks.settings; + +import android.os.Bundle; + +import androidx.preference.PreferenceFragmentCompat; + +import de.dennisguse.opentracks.BuildConfig; +import de.dennisguse.opentracks.R; + +public class AggregatedStatsSettingsFragment extends PreferenceFragmentCompat { + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.settings_aggregated_stats); + } + + @Override + public void onStart() { + super.onStart(); + ((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_aggregated_stats); + findPreference(getString(R.string.settings_aggregated_stats_key)); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/settings/MainSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/MainSettingsFragment.java index f2fd0bfbf..da1bf367f 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/MainSettingsFragment.java +++ b/src/main/java/de/dennisguse/opentracks/settings/MainSettingsFragment.java @@ -49,6 +49,16 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { ((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_api_key)); return true; }); + + findPreference(getString(R.string.settings_aggregated_stats_key)).setOnPreferenceClickListener(preference -> { + ((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_aggregated_stats_key)); + return true; + }); + + findPreference(getString(R.string.user_profile)).setOnPreferenceClickListener(preference -> { + ((SettingsActivity) getActivity()).openScreen(getString(R.string.user_profile)); + return true; + }); } @Override diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java index 9ed4ee1ab..cda505284 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java @@ -462,6 +462,46 @@ public static void setVoiceAnnounceAverageHeartRate(boolean value) { setBoolean(R.string.voice_announce_average_heart_rate_key, value); } + public static boolean shouldElevationGain() { + return getBoolean(R.string.aggregated_stats_package_key, false); + } + + @VisibleForTesting + public static void setElevationGain(boolean value) { + setBoolean(R.string.aggregated_stats_package_key, value); + } + + public static boolean shouldDistance() { + Log.d("myLogs", String.valueOf(R.string.distance_preference_key)); + return getBoolean(R.string.distance_preference_key, false); + } + + @VisibleForTesting + public static void setDistance(boolean value) { + setBoolean(R.string.distance_preference_key, value); + } + + public static boolean shouldMaxSpeed() { + return getBoolean(R.string.max_speed_preference_key, false); + } + + @VisibleForTesting + public static void setMaxSpeed(boolean value) { + setBoolean(R.string.distance_preference_key, value); + } + + public static boolean shouldMovingSpeed() { + return getBoolean(R.string.moving_speed_preference_key, false); + } + + @VisibleForTesting + public static void setMovingSpeed(boolean value) { + setBoolean(R.string.moving_speed_preference_key, value); + } + public static boolean shouldMovingTime() { + return getBoolean(R.string.moving_time_preference_key, false); + } + public static Distance getRecordingDistanceInterval() { return Distance.of(getInt(R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefaultInternal())); } diff --git a/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java b/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java index 2f22e0d45..b3ffd3880 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java +++ b/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java @@ -94,8 +94,12 @@ private PreferenceFragmentCompat getPreferenceScreen(String key) { fragment = new ImportExportSettingsFragment(); } else if (key.equals(getString(R.string.settings_api_key))) { fragment = new PublicAPISettingsFragment(); + } else if (key.equals(getString(R.string.settings_aggregated_stats_key))) { + fragment = new AggregatedStatsSettingsFragment(); + } else if (key.equals(getString(R.string.user_profile))) { + fragment = new UserProfileSettingsFragment(); } - + return fragment; } diff --git a/src/main/java/de/dennisguse/opentracks/settings/UserProfileSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/UserProfileSettingsFragment.java new file mode 100644 index 000000000..a6e4c783f --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/settings/UserProfileSettingsFragment.java @@ -0,0 +1,22 @@ +package de.dennisguse.opentracks.settings; + +import android.os.Bundle; + +import androidx.preference.PreferenceFragmentCompat; + +import de.dennisguse.opentracks.BuildConfig; +import de.dennisguse.opentracks.R; + +public class UserProfileSettingsFragment extends PreferenceFragmentCompat { + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.settings_user_profile); + } + + @Override + public void onStart() { + super.onStart(); + + } +} diff --git a/src/main/java/de/dennisguse/opentracks/ui/.DS_Store b/src/main/java/de/dennisguse/opentracks/ui/.DS_Store new file mode 100644 index 000000000..28a20b109 Binary files /dev/null and b/src/main/java/de/dennisguse/opentracks/ui/.DS_Store differ diff --git a/src/main/java/de/dennisguse/opentracks/ui/aggregatedStatistics/AggregatedStatisticsAdapter.java b/src/main/java/de/dennisguse/opentracks/ui/aggregatedStatistics/AggregatedStatisticsAdapter.java index 9cb2e7c8f..add67c6fc 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/aggregatedStatistics/AggregatedStatisticsAdapter.java +++ b/src/main/java/de/dennisguse/opentracks/ui/aggregatedStatistics/AggregatedStatisticsAdapter.java @@ -1,6 +1,7 @@ package de.dennisguse.opentracks.ui.aggregatedStatistics; import android.content.Context; +import android.util.Log; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; @@ -20,7 +21,13 @@ import de.dennisguse.opentracks.data.models.SpeedFormatter; import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.UnitSystem; +import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.util.StringUtils; +import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldElevationGain; +import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldDistance; +import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldMaxSpeed; +import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldMovingSpeed; +import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldMovingTime; public class AggregatedStatisticsAdapter extends RecyclerView.Adapter { @@ -48,6 +55,35 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi String type = aggregatedStatistic.getActivityTypeLocalized(); if (ActivityType.findByLocalizedString(context, type).isShowSpeedPreferred()) { viewHolder.setSpeed(aggregatedStatistic); + if(shouldElevationGain()) { + viewHolder.setElevation(aggregatedStatistic); + } + if(shouldDistance()) { + Log.d("myLogs", "ShouldDistance is called"); + viewHolder.setDistance(aggregatedStatistic, true); + } + else { + Log.d("myLogs", "Inside Else"); + viewHolder.setDistance(aggregatedStatistic, false); + } + if(shouldMaxSpeed()) { + viewHolder.setMaxSpeed(aggregatedStatistic, true); + } + else { + viewHolder.setMaxSpeed(aggregatedStatistic, false); + } + if(shouldMovingSpeed()){ + viewHolder.setMovingSpeed(aggregatedStatistic, true); + } + else { + viewHolder.setMovingSpeed(aggregatedStatistic, false); + } + if(shouldMovingTime()){ + viewHolder.setMovingTime(aggregatedStatistic, true); + } + else { + viewHolder.setMovingTime(aggregatedStatistic, false); + } } else { viewHolder.setPace(aggregatedStatistic); } @@ -89,6 +125,10 @@ private class ViewHolder extends RecyclerView.ViewHolder { private final TextView maxSpeedUnit; private final TextView maxSpeedLabel; + private TextView elevationGain = null; + private TextView elevationLabel = null; + private TextView elevationUnit = null; + private UnitSystem unitSystem = UnitSystem.defaultUnitSystem(); private boolean reportSpeed; @@ -100,15 +140,107 @@ public ViewHolder(View view) { distance = view.findViewById(R.id.aggregated_stats_distance); distanceUnit = view.findViewById(R.id.aggregated_stats_distance_unit); time = view.findViewById(R.id.aggregated_stats_time); - avgSpeed = view.findViewById(R.id.aggregated_stats_avg_rate); avgSpeedUnit = view.findViewById(R.id.aggregated_stats_avg_rate_unit); avgSpeedLabel = view.findViewById(R.id.aggregated_stats_avg_rate_label); maxSpeed = view.findViewById(R.id.aggregated_stats_max_rate); maxSpeedUnit = view.findViewById(R.id.aggregated_stats_max_rate_unit); maxSpeedLabel = view.findViewById(R.id.aggregated_stats_max_rate_label); + if(shouldElevationGain()) { + elevationGain = view.findViewById(R.id.aggregated_stats_elevation_gain); + elevationLabel = view.findViewById(R.id.aggregated_stats_elevation_gain_label); + elevationUnit = view.findViewById(R.id.aggregated_stats_elevation_gain_unit); + + elevationLabel.setText("Elevation Gain"); + elevationUnit.setText("ft"); + } + } + public void setElevation(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) { + if(aggregatedStatistic.getTrackStatistics().getTotalAltitudeGain()==null) + elevationGain.setText("0.0"); + else + elevationGain.setText(aggregatedStatistic.getTrackStatistics().getTotalAltitudeGain().toString()); + System.out.println(); + } + + public void setDistance(AggregatedStatistics.AggregatedStatistic aggregatedStatistic, Boolean visibility) { + if(visibility) { + if (aggregatedStatistic.getTrackStatistics().getTotalDistance() == null) { + Log.d("myLogs", "aggregate value is null"); + distance.setText("0.0"); + } else { + Log.d("myLogs", "Visibility true, setting value in else"); + Pair parts = DistanceFormatter.Builder() + .setUnit(unitSystem) + .build(context).getDistanceParts(aggregatedStatistic.getTrackStatistics().getTotalDistance()); + distance.setText(parts.first); + distanceUnit.setText(parts.second); + } + } + else{ + Log.d("myLogs", "Visibility is false"); + distance.setText(""); + distanceUnit.setText(""); + } } + public void setMaxSpeed(AggregatedStatistics.AggregatedStatistic aggregatedStatistic, Boolean visibility) { + SpeedFormatter formatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context); + if(visibility){ + if(aggregatedStatistic.getTrackStatistics().getMaxSpeed() ==null) + maxSpeed.setText("0.0"); + + else + { + Pair parts = formatter.getSpeedParts(aggregatedStatistic.getTrackStatistics().getMaxSpeed()); + maxSpeed.setText(parts.first); + maxSpeedUnit.setText(parts.second); + maxSpeedLabel.setText(context.getString(R.string.stats_max_speed)); + } + } + else{ + maxSpeed.setText(""); + maxSpeedUnit.setText(""); + maxSpeedLabel.setText(""); + } + + } + + public void setMovingSpeed(AggregatedStatistics.AggregatedStatistic aggregatedStatistic, Boolean visibility) { + SpeedFormatter formatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context); + if(visibility){ + if(aggregatedStatistic.getTrackStatistics().getMaxSpeed() ==null) + avgSpeed.setText("0.0"); + else + { + Pair parts = formatter.getSpeedParts(aggregatedStatistic.getTrackStatistics().getMaxSpeed()); + avgSpeed.setText(parts.first); + avgSpeedUnit.setText(parts.second); + avgSpeedLabel.setText(context.getString(R.string.stats_max_speed)); + } + } + else{ + avgSpeed.setText(""); + avgSpeedUnit.setText(""); + avgSpeedLabel.setText(""); + } + + } + public void setMovingTime(AggregatedStatistics.AggregatedStatistic aggregatedStatistic, Boolean visibility) { + SpeedFormatter formatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context); + if(visibility){ + if(aggregatedStatistic.getTrackStatistics().getMaxSpeed() ==null) + time.setText("0.0"); + else + { + time.setText(StringUtils.formatElapsedTime(aggregatedStatistic.getTrackStatistics().getMovingTime())); + } + } + else{ + time.setText(""); + } + + } public void setSpeed(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) { setCommonValues(aggregatedStatistic); diff --git a/src/main/res/.DS_Store b/src/main/res/.DS_Store new file mode 100644 index 000000000..f4ef853db Binary files /dev/null and b/src/main/res/.DS_Store differ diff --git a/src/main/res/drawable/ic_activity_timer_24dp.xml b/src/main/res/drawable/ic_activity_timer_24dp.xml new file mode 100644 index 000000000..623b19af8 --- /dev/null +++ b/src/main/res/drawable/ic_activity_timer_24dp.xml @@ -0,0 +1,24 @@ + + + + + + + + + diff --git a/src/main/res/drawable/ic_baseline_play_arrow_24.xml b/src/main/res/drawable/ic_baseline_play_arrow_24.xml new file mode 100644 index 000000000..88a06e5d4 --- /dev/null +++ b/src/main/res/drawable/ic_baseline_play_arrow_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/start.xml b/src/main/res/drawable/start.xml new file mode 100644 index 000000000..c2604bd10 --- /dev/null +++ b/src/main/res/drawable/start.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/src/main/res/drawable/stop.xml b/src/main/res/drawable/stop.xml new file mode 100644 index 000000000..7df5edba9 --- /dev/null +++ b/src/main/res/drawable/stop.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/src/main/res/drawable/user_profile.xml b/src/main/res/drawable/user_profile.xml new file mode 100644 index 000000000..8e6c07b50 --- /dev/null +++ b/src/main/res/drawable/user_profile.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/layout/aggregated_stats_list_item.xml b/src/main/res/layout/aggregated_stats_list_item.xml index 7b148cb0f..99087e414 100644 --- a/src/main/res/layout/aggregated_stats_list_item.xml +++ b/src/main/res/layout/aggregated_stats_list_item.xml @@ -66,6 +66,7 @@ app:layout_constraintTop_toTopOf="@+id/aggregated_stats_type_label" tools:text="(1)" /> + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/aggregated_stats_elevation_gain_label" /> \ No newline at end of file diff --git a/src/main/res/layout/interval_list_view.xml b/src/main/res/layout/interval_list_view.xml index aaf36bd8b..f518f9f8c 100644 --- a/src/main/res/layout/interval_list_view.xml +++ b/src/main/res/layout/interval_list_view.xml @@ -14,7 +14,7 @@ android:layout_marginBottom="8dp" android:hint="@string/stats_split_interval"> - + tools:text="100" + /> + - - - \ No newline at end of file + android:layout_weight="1" + android:backgroundTint="#0B0A0A" + android:fontFamily="monospace" + android:text="START WORKOUT" + android:textStyle="bold" + app:layout_anchor="@+id/bottom_app_bar" + app:layout_anchorGravity="center" /> + + diff --git a/src/main/res/layout/track_recording.xml b/src/main/res/layout/track_recording.xml index 360a056dc..a79743373 100644 --- a/src/main/res/layout/track_recording.xml +++ b/src/main/res/layout/track_recording.xml @@ -1,16 +1,30 @@ + android:layout_height="match_parent"> + + + + + + app:menu="@menu/track_record" + app:navigationIcon="@drawable/ic_logo_color_24dp" /> - + android:backgroundTint="#020202" + android:fontFamily="monospace" + android:text="HOLD TO PAUSE" + android:textStyle="bold" + android:translationX="-20dp" + app:layout_anchor="@+id/bottom_app_bar" + app:layout_anchorGravity="end|center" /> + + - \ No newline at end of file diff --git a/src/main/res/layout/track_stopped.xml b/src/main/res/layout/track_stopped.xml index 4aca4c419..c2f4a1867 100644 --- a/src/main/res/layout/track_stopped.xml +++ b/src/main/res/layout/track_stopped.xml @@ -197,27 +197,33 @@ app:tint="?attr/colorOnBackground" app:srcCompat="@drawable/ic_delete_forever_24dp" /> - - - + +