mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-24 03:05:17 +02:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5594a125d0 | |||
| 13380afef0 | |||
| 9c4c8131f5 | |||
| 7f10ae5c3f | |||
| 1fa45a3393 | |||
| f1b0c83196 | |||
| f0f7bca54b | |||
| 8f39bf139c | |||
| 85585969bd | |||
| 976e7be370 | |||
| 48dc64d4d7 | |||
| 16e69e850b | |||
| 5ece301ac0 | |||
| a5e500cdb1 |
+4
-4
@@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.0'
|
||||
classpath 'com.android.tools.build:gradle:7.0.1'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 4396
|
||||
versionName "v3.19.1"
|
||||
versionCode 4410
|
||||
versionName "v3.20.0"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -98,7 +98,7 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:3.10.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:3.12.0'
|
||||
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.0'
|
||||
}
|
||||
|
||||
+21
-2
@@ -43,7 +43,6 @@ class TrackPoint {
|
||||
- sensor_power
|
||||
}
|
||||
|
||||
|
||||
class Marker {
|
||||
- id
|
||||
- trackId
|
||||
@@ -58,6 +57,26 @@ class Marker {
|
||||
- photoUrl
|
||||
}
|
||||
|
||||
class DataField {
|
||||
- key
|
||||
- title
|
||||
- isVisible
|
||||
- isPrimary
|
||||
- isWide
|
||||
}
|
||||
|
||||
class StatisticData {
|
||||
- value
|
||||
- unit
|
||||
- description
|
||||
}
|
||||
StatisticData o-- DataField
|
||||
|
||||
class Layout {
|
||||
- profile
|
||||
}
|
||||
Layout "1" o-- "many" DataField
|
||||
|
||||
note left of TrackPoint
|
||||
As of OpenTracks version 3.15.0, all times are using device time.
|
||||
Before that TrackPoint.time contained GPS time (determined by GPS hardware).
|
||||
@@ -77,4 +96,4 @@ Trackpoints of Type.SEGMENT_START_MANUAL or Type.SEGMENT_END_MANUAL do not conta
|
||||
|
||||
Tracks recorded prior to OpenTracks version 3.15.0, do neither begin with a Type.SEGMENT_START_MANUAL nor end with a Type.SEGMENT_END_MANUAL.
|
||||
end note
|
||||
@enduml
|
||||
@enduml
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
v3.20.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- While recording: statistics UI is now configurable
|
||||
|
||||
Bugfix:
|
||||
- Changing track name/ category while recording was ignored (introduced in v3.19.0)
|
||||
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
|
||||
import static androidx.test.espresso.Espresso.onData;
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.scrollTo;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.anything;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import androidx.test.espresso.DataInteraction;
|
||||
import androidx.test.espresso.UiController;
|
||||
import androidx.test.espresso.ViewAction;
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Espresso test that checks the unit for activity category selected.
|
||||
* Also it checks that unit is properly when user change from a pace activity to an speed one.
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EspressoCategoryUnitTest {
|
||||
|
||||
@Rule
|
||||
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public TestRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
private final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
@Rule
|
||||
public TestRule mLocaleRule = new PreferenceMetricUnitRule(context, true);
|
||||
|
||||
@Test
|
||||
public void changeBetweenCategoryUpdateProperlyUnitTest() {
|
||||
// Start record.
|
||||
ViewInteraction appCompatImageButton = onView(
|
||||
allOf(withId(R.id.controller_record), withContentDescription("Record"),
|
||||
childAtPosition(
|
||||
allOf(withId(R.id.controller_container),
|
||||
childAtPosition(
|
||||
withId(R.id.controller_fragment),
|
||||
0)),
|
||||
0),
|
||||
isDisplayed()));
|
||||
appCompatImageButton.perform(click());
|
||||
|
||||
{
|
||||
// Change to running activity (pace activity).
|
||||
ViewInteraction appCompatImageView = onView(
|
||||
allOf(withId(R.id.stats_activity_type_icon),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withClassName(is("android.widget.ScrollView")),
|
||||
0),
|
||||
7)));
|
||||
appCompatImageView.perform(scrollTo(), click());
|
||||
|
||||
DataInteraction imageView = onData(anything())
|
||||
.inAdapterView(allOf(withId(R.id.choose_activity_type_grid_view),
|
||||
childAtPosition(
|
||||
withClassName(is("android.widget.LinearLayout")),
|
||||
0)))
|
||||
.atPosition(1);
|
||||
imageView.perform(click());
|
||||
}
|
||||
|
||||
// Pace (min/km) in all fields.
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_speed_unit)));
|
||||
textView.perform(waitFor(2000));
|
||||
textView.check(matches(withText("min/km")));
|
||||
}
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_average_speed_unit)));
|
||||
textView.check(matches(withText("min/km")));
|
||||
}
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_max_speed_unit)));
|
||||
textView.check(matches(withText("min/km")));
|
||||
}
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_moving_speed_unit)));
|
||||
textView.check(matches(withText("min/km")));
|
||||
}
|
||||
|
||||
{
|
||||
// Change to biking activity (speed activity).
|
||||
ViewInteraction appCompatImageView = onView(
|
||||
allOf(withId(R.id.stats_activity_type_icon),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withClassName(is("android.widget.ScrollView")),
|
||||
0),
|
||||
7)));
|
||||
appCompatImageView.perform(scrollTo(), click());
|
||||
|
||||
DataInteraction imageView = onData(anything())
|
||||
.inAdapterView(allOf(withId(R.id.choose_activity_type_grid_view),
|
||||
childAtPosition(
|
||||
withClassName(is("android.widget.LinearLayout")),
|
||||
0)))
|
||||
.atPosition(9);
|
||||
imageView.perform(click());
|
||||
}
|
||||
|
||||
// Speed (km/h) in all fields.
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_speed_unit)));
|
||||
textView.perform(waitFor(2000));
|
||||
textView.check(matches(withText("km/h")));
|
||||
}
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_average_speed_unit)));
|
||||
textView.check(matches(withText("km/h")));
|
||||
}
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_max_speed_unit)));
|
||||
textView.check(matches(withText("km/h")));
|
||||
}
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_moving_speed_unit)));
|
||||
textView.check(matches(withText("km/h")));
|
||||
}
|
||||
}
|
||||
|
||||
private static Matcher<View> childAtPosition(
|
||||
final Matcher<View> 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));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ViewAction waitFor(final long duration_ms) {
|
||||
return new ViewAction() {
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Wait for milliseconds.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matcher<View> getConstraints() {
|
||||
return isDisplayed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(UiController uiController, final View view) {
|
||||
uiController.loopMainThreadForAtLeast(duration_ms);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.espresso.NoMatchingViewException;
|
||||
import androidx.test.espresso.UiController;
|
||||
import androidx.test.espresso.ViewAction;
|
||||
import androidx.test.espresso.ViewAssertion;
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.espresso.action.MotionEvents;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.*;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.*;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EspressoCustomLayoutTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<TrackListActivity> mActivityTestRule = new ActivityTestRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule =
|
||||
GrantPermissionRule.grant(
|
||||
"android.permission.ACCESS_FINE_LOCATION");
|
||||
|
||||
@Test
|
||||
public void customLayoutTest() {
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
trackControllerRecordButton.perform(click());
|
||||
|
||||
// Get custom layout preferences and check all data fields are showed.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
|
||||
|
||||
onView(withId(R.id.stats_recycler_view)).check(new RecyclerViewItemCountAssertion((int) layout.getFields().stream().filter(DataField::isVisible).count()));
|
||||
|
||||
// stop recording
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
||||
}
|
||||
|
||||
private static class RecyclerViewItemCountAssertion implements ViewAssertion {
|
||||
private final int expectedCount;
|
||||
|
||||
public RecyclerViewItemCountAssertion(int expectedCount) {
|
||||
this.expectedCount = expectedCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check(View view, NoMatchingViewException noViewFoundException) {
|
||||
if (noViewFoundException != null) {
|
||||
throw noViewFoundException;
|
||||
}
|
||||
|
||||
RecyclerView recyclerView = (RecyclerView) view;
|
||||
RecyclerView.Adapter adapter = recyclerView.getAdapter();
|
||||
assertThat(adapter.getItemCount(), is(expectedCount));
|
||||
}
|
||||
}
|
||||
|
||||
private static Matcher<View> childAtPosition(
|
||||
final Matcher<View> parentMatcher, final int position) {
|
||||
|
||||
return new TypeSafeMatcher<View>() {
|
||||
@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));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ViewAction veryLongTouch(final int duration_ms) {
|
||||
return new ViewAction() {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Perform long touch.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matcher<View> getConstraints() {
|
||||
return isDisplayed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(UiController uiController, final View view) {
|
||||
// Get view absolute position
|
||||
int[] location = new int[2];
|
||||
view.getLocationOnScreen(location);
|
||||
|
||||
// Offset coordinates by view position
|
||||
float[] coordinates = new float[]{location[0] + 1, location[1] + 1};
|
||||
|
||||
// Send down event, pause, and send up
|
||||
MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down;
|
||||
uiController.loopMainThreadForAtLeast(duration_ms);
|
||||
MotionEvents.sendUp(uiController, down, coordinates);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
||||
import static androidx.test.espresso.action.ViewActions.replaceText;
|
||||
import static androidx.test.espresso.action.ViewActions.scrollTo;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withParent;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import androidx.test.espresso.PerformException;
|
||||
import androidx.test.espresso.UiController;
|
||||
import androidx.test.espresso.ViewAction;
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.espresso.action.MotionEvents;
|
||||
import androidx.test.espresso.util.HumanReadables;
|
||||
import androidx.test.espresso.util.TreeIterables;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
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 java.util.concurrent.TimeoutException;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EspressoEditTrackRecordingTest {
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<TrackListActivity> mActivityTestRule = new ActivityTestRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule =
|
||||
GrantPermissionRule.grant(
|
||||
"android.permission.ACCESS_FINE_LOCATION",
|
||||
"android.permission.ACCESS_COARSE_LOCATION");
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void espressoEditTrackRecordingTest() {
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
trackControllerRecordButton.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||
|
||||
// wait; stay recording
|
||||
trackControllerRecordButton.perform(waitFor(15000));
|
||||
|
||||
// open menu
|
||||
ViewInteraction overflowMenuButton = onView(
|
||||
allOf(withContentDescription("More options"),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withId(R.id.toolbar),
|
||||
2),
|
||||
2),
|
||||
isDisplayed()));
|
||||
overflowMenuButton.perform(click());
|
||||
|
||||
// click on edit
|
||||
ViewInteraction appCompatTextView = onView(
|
||||
allOf(withId(R.id.title), withText("Edit"),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withId(R.id.content),
|
||||
0),
|
||||
0),
|
||||
isDisplayed()));
|
||||
appCompatTextView.perform(click());
|
||||
|
||||
// change name for "New Name"
|
||||
ViewInteraction textInputEditText = onView(
|
||||
allOf(withId(R.id.track_edit_name),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withClassName(is("com.google.android.material.textfield.TextInputLayout")),
|
||||
0),
|
||||
1)));
|
||||
textInputEditText.perform(scrollTo(), replaceText("New Name"));
|
||||
|
||||
ViewInteraction textInputEditText2 = onView(
|
||||
allOf(withId(R.id.track_edit_name), withText("New Name"),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withClassName(is("com.google.android.material.textfield.TextInputLayout")),
|
||||
0),
|
||||
1),
|
||||
isDisplayed()));
|
||||
textInputEditText2.perform(closeSoftKeyboard());
|
||||
|
||||
// save edition
|
||||
ViewInteraction appCompatButton = onView(
|
||||
allOf(withId(R.id.track_edit_save),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withClassName(is("android.widget.LinearLayout")),
|
||||
3),
|
||||
1),
|
||||
isDisplayed()));
|
||||
appCompatButton.perform(click());
|
||||
|
||||
//pause
|
||||
trackControllerRecordButton.perform(veryLongTouch(1600));
|
||||
|
||||
// wait; stay paused
|
||||
trackControllerRecordButton.perform(waitFor(1000));
|
||||
|
||||
// resume
|
||||
trackControllerRecordButton.perform(click());
|
||||
|
||||
// wait; stay recording
|
||||
trackControllerRecordButton.perform(waitFor(1000));
|
||||
|
||||
// stop;
|
||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
||||
|
||||
// check track's name is "New Name"
|
||||
ViewInteraction textView = onView(
|
||||
allOf(withId(R.id.stats_name_value),
|
||||
withParent(withParent(IsInstanceOf.<View>instanceOf(android.widget.ScrollView.class))),
|
||||
isDisplayed()));
|
||||
textView.check(matches(withText("New Name")));
|
||||
}
|
||||
}
|
||||
|
||||
private static Matcher<View> childAtPosition(
|
||||
final Matcher<View> parentMatcher, final int position) {
|
||||
|
||||
return new TypeSafeMatcher<View>() {
|
||||
@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));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static ViewAction waitId(final int viewId, final long millis) {
|
||||
return new ViewAction() {
|
||||
@Override
|
||||
public Matcher<View> getConstraints() {
|
||||
return isRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "wait for a specific view with id <" + viewId + "> during " + millis + " millis.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(final UiController uiController, final View view) {
|
||||
uiController.loopMainThreadUntilIdle();
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final long endTime = startTime + millis;
|
||||
final Matcher<View> viewMatcher = withId(viewId);
|
||||
|
||||
do {
|
||||
for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
|
||||
// found view with required ID
|
||||
if (viewMatcher.matches(child)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uiController.loopMainThreadForAtLeast(50);
|
||||
}
|
||||
while (System.currentTimeMillis() < endTime);
|
||||
|
||||
// timeout happens
|
||||
throw new PerformException.Builder()
|
||||
.withActionDescription(this.getDescription())
|
||||
.withViewDescription(HumanReadables.describe(view))
|
||||
.withCause(new TimeoutException())
|
||||
.build();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ViewAction waitFor(final long duration_ms) {
|
||||
return new ViewAction() {
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Wait for milliseconds.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matcher<View> getConstraints() {
|
||||
return isDisplayed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(UiController uiController, final View view) {
|
||||
uiController.loopMainThreadForAtLeast(duration_ms);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ViewAction veryLongTouch(final int duration_ms) {
|
||||
return new ViewAction() {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Perform long touch.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matcher<View> getConstraints() {
|
||||
return isDisplayed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(UiController uiController, final View view) {
|
||||
// Get view absolute position
|
||||
int[] location = new int[2];
|
||||
view.getLocationOnScreen(location);
|
||||
|
||||
// Offset coordinates by view position
|
||||
float[] coordinates = new float[]{location[0] + 1, location[1] + 1};
|
||||
|
||||
// Send down event, pause, and send up
|
||||
MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down;
|
||||
uiController.loopMainThreadForAtLeast(duration_ms);
|
||||
MotionEvents.sendUp(uiController, down, coordinates);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,14 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PreferencesUtilsTest {
|
||||
@@ -64,4 +69,131 @@ public class PreferencesUtilsTest {
|
||||
// then
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCustomLayout_default() {
|
||||
// given
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.clear();
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertTrue(layout.getFields().size() > 0);
|
||||
assertEquals(layout.getProfile(), context.getString(R.string.default_activity_default));
|
||||
assertTrue(layout.getFields().stream().anyMatch(DataField::isVisible));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCustomLayout_1() {
|
||||
// given
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(
|
||||
context.getString(R.string.stats_custom_layout_fields_key),
|
||||
"run;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
|
||||
editor.apply();
|
||||
|
||||
// when
|
||||
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertEquals(layout.getFields().size(), 4);
|
||||
assertEquals(layout.getProfile(), "run");
|
||||
|
||||
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||
assertTrue(layout.getFields().get(0).isVisible());
|
||||
assertTrue(layout.getFields().get(0).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||
assertTrue(layout.getFields().get(1).isVisible());
|
||||
assertFalse(layout.getFields().get(1).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_average_moving_speed_key));
|
||||
assertFalse(layout.getFields().get(2).isVisible());
|
||||
assertTrue(layout.getFields().get(2).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||
assertFalse(layout.getFields().get(3).isVisible());
|
||||
assertFalse(layout.getFields().get(3).isPrimary());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCustomLayout_coordinatesIsWide() {
|
||||
// given
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(
|
||||
context.getString(R.string.stats_custom_layout_fields_key),
|
||||
"walking;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_coordinates_key) + ",0,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
|
||||
editor.apply();
|
||||
|
||||
// when
|
||||
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertEquals(layout.getFields().size(), 4);
|
||||
assertEquals(layout.getProfile(), "walking");
|
||||
|
||||
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||
assertTrue(layout.getFields().get(0).isVisible());
|
||||
assertTrue(layout.getFields().get(0).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||
assertTrue(layout.getFields().get(1).isVisible());
|
||||
assertFalse(layout.getFields().get(1).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_coordinates_key));
|
||||
assertFalse(layout.getFields().get(2).isVisible());
|
||||
assertTrue(layout.getFields().get(2).isPrimary());
|
||||
assertTrue(layout.getFields().get(2).isWide());
|
||||
|
||||
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||
assertFalse(layout.getFields().get(3).isVisible());
|
||||
assertFalse(layout.getFields().get(3).isPrimary());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetCustomLayout() {
|
||||
// given
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
Layout layoutSrc = new Layout("road cycling");
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false));
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, false, false));
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), false, true, false));
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, false, false));
|
||||
|
||||
// when
|
||||
PreferencesUtils.setCustomLayout(sharedPreferences, context, layoutSrc);
|
||||
|
||||
// then
|
||||
String csv = sharedPreferences.getString(context.getString(R.string.stats_custom_layout_fields_key), null);
|
||||
assertNotNull(csv);
|
||||
assertEquals(csv,
|
||||
"road cycling;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
|
||||
|
||||
Layout layoutDst = PreferencesUtils.getCustomLayout(sharedPreferences, context);
|
||||
assertEquals(layoutSrc.getProfile(), layoutDst.getProfile());
|
||||
assertEquals(layoutSrc.getFields().size(), layoutDst.getFields().size());
|
||||
for (int i = 0; i < layoutSrc.getFields().size(); i++) {
|
||||
assertEquals(layoutSrc.getFields().get(i).getKey(), layoutDst.getFields().get(i).getKey());
|
||||
assertEquals(layoutSrc.getFields().get(i).isVisible(), layoutDst.getFields().get(i).isVisible());
|
||||
assertEquals(layoutSrc.getFields().get(i).isPrimary(), layoutDst.getFields().get(i).isPrimary());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class StatisticsUtilsTest extends TestCase {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void testEmptyValue() {
|
||||
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_distance_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_total_time_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_speed_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_moving_time_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_average_speed_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_max_speed_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_average_moving_speed_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_altitude_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_gain_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_loss_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_pace_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_average_moving_pace_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_average_pace_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_fastest_pace_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_coordinates), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_coordinates_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_heart_rate_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_cadence_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_power_key)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilterVisible() {
|
||||
// given
|
||||
Layout layout = new Layout("profile");
|
||||
layout.addField(new DataField("key1", "title1", true, true, false));
|
||||
layout.addField(new DataField("key2", "title2", false, true, false));
|
||||
layout.addField(new DataField("key3", "title3", true, true, false));
|
||||
layout.addField(new DataField("key4", "title4", false, true, false));
|
||||
layout.addField(new DataField("key5", "title5", true, true, false));
|
||||
|
||||
// when
|
||||
Layout resultTrue = StatisticsUtils.filterVisible(layout, true);
|
||||
Layout resultFalse = StatisticsUtils.filterVisible(layout, false);
|
||||
|
||||
// then
|
||||
assertEquals(resultTrue.getFields().size(), 3);
|
||||
assertTrue(resultTrue.getFields().stream().anyMatch(f -> f.getKey().equals("key1")));
|
||||
assertTrue(resultTrue.getFields().stream().anyMatch(f -> f.getKey().equals("key3")));
|
||||
assertTrue(resultTrue.getFields().stream().anyMatch(f -> f.getKey().equals("key5")));
|
||||
|
||||
assertEquals(resultFalse.getFields().size(), 2);
|
||||
assertTrue(resultFalse.getFields().stream().anyMatch(f -> f.getKey().equals("key2")));
|
||||
assertTrue(resultFalse.getFields().stream().anyMatch(f -> f.getKey().equals("key4")));
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class StatisticDataBuilderTest extends TestCase {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Mock
|
||||
private Track trackMock;
|
||||
|
||||
@Mock
|
||||
private TrackStatistics trackStatisticsMock;
|
||||
|
||||
@Mock
|
||||
private TrackRecordingService.RecordingData recordingDataMock;
|
||||
|
||||
@Mock
|
||||
private SensorStatistics sensorStatisticsMock;
|
||||
|
||||
@Test
|
||||
public void testFromRecordingData() {
|
||||
when(trackMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
||||
when(trackStatisticsMock.getTotalTime()).thenReturn(Duration.ofMillis(0));
|
||||
when(trackStatisticsMock.getMovingTime()).thenReturn(Duration.ofMillis(0));
|
||||
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
|
||||
when(recordingDataMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
||||
|
||||
// given
|
||||
Layout layout = new Layout(context.getString(R.string.default_activity_default));
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_total_time_key), context.getString(R.string.stats_total_time), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_max_speed_key), context.getString(R.string.stats_max_speed), false, true, false);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, true);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 3);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_total_time))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_moving_time))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_distance))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromRecordingData_allFields() {
|
||||
when(trackMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
||||
when(trackStatisticsMock.getTotalTime()).thenReturn(Duration.ofMillis(0));
|
||||
when(trackStatisticsMock.getMovingTime()).thenReturn(Duration.ofMillis(0));
|
||||
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
|
||||
when(recordingDataMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
||||
|
||||
// given
|
||||
Layout layout = new Layout(context.getString(R.string.default_activity_default));
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_total_time_key), context.getString(R.string.stats_total_time), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_average_speed_key), context.getString(R.string.stats_average_speed), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_max_speed_key), context.getString(R.string.stats_max_speed), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_pace_key), context.getString(R.string.stats_pace), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_average_moving_pace_key), context.getString(R.string.stats_average_moving_pace), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_average_pace_key), context.getString(R.string.stats_average_pace), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_fastest_pace_key), context.getString(R.string.stats_fastest_pace), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_altitude_key), context.getString(R.string.stats_altitude), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_gain_key), context.getString(R.string.stats_gain), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_loss_key), context.getString(R.string.stats_loss), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_coordinates_key), context.getString(R.string.stats_coordinates), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_heart_rate_key), context.getString(R.string.stats_sensors_heart_rate), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_cadence_key), context.getString(R.string.stats_sensors_cadence), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_power_key), context.getString(R.string.stats_sensors_power), true, true, false);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, true);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 18);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_total_time))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_moving_time))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_distance))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_speed))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_moving_speed))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_speed))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_max_speed))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_pace))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_moving_pace))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_pace))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_fastest_pace))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_altitude))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_gain))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_loss))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_coordinates))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_power))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromSensorStatistics_onlyHeartRate() {
|
||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(200f);
|
||||
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(150f);
|
||||
when(sensorStatisticsMock.hasCadence()).thenReturn(false);
|
||||
when(sensorStatisticsMock.hasPower()).thenReturn(false);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 2);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_max))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_avg))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromSensorStatistics_onlyCadence() {
|
||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(false);
|
||||
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getAvgCadence()).thenReturn(90f);
|
||||
when(sensorStatisticsMock.getMaxCadence()).thenReturn(110f);
|
||||
when(sensorStatisticsMock.hasPower()).thenReturn(false);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 2);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_max))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_avg))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromSensorStatistics_onlyPower() {
|
||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(false);
|
||||
when(sensorStatisticsMock.hasCadence()).thenReturn(false);
|
||||
when(sensorStatisticsMock.hasPower()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getAvgPower()).thenReturn(300f);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_power_avg))));
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromSensorStatistics() {
|
||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(200f);
|
||||
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(150f);
|
||||
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getAvgCadence()).thenReturn(90f);
|
||||
when(sensorStatisticsMock.getMaxCadence()).thenReturn(110f);
|
||||
when(sensorStatisticsMock.hasPower()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getAvgPower()).thenReturn(300f);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 5);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_max))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_avg))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_max))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_avg))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_power_avg))));
|
||||
}
|
||||
}
|
||||
@@ -316,6 +316,7 @@ limitations under the License.
|
||||
<activity android:name=".io.file.exporter.ExportActivity" />
|
||||
|
||||
<activity android:name=".settings.SettingsActivity" />
|
||||
<activity android:name=".settings.SettingsCustomLayoutActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".ShowOnMapProxyActivity$KML"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
enum CustomLayoutFieldType {
|
||||
SHORT(0),
|
||||
WIDE(1);
|
||||
|
||||
private final int value;
|
||||
|
||||
CustomLayoutFieldType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int value() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
|
||||
|
||||
public class SensorsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private List<SensorDataModel> sensorDataList;
|
||||
private final Context context;
|
||||
|
||||
public SensorsAdapter(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.sensor_item, parent, false);
|
||||
return new SensorsAdapter.ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
SensorsAdapter.ViewHolder viewHolder = (SensorsAdapter.ViewHolder) holder;
|
||||
SensorDataModel sensorDataModel = sensorDataList.get(position);
|
||||
viewHolder.setData(sensorDataModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (sensorDataList == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return sensorDataList.size();
|
||||
}
|
||||
}
|
||||
|
||||
public List<SensorDataModel> swapData(List<SensorDataModel> data) {
|
||||
if (sensorDataList == data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
sensorDataList = data;
|
||||
|
||||
if (data != null) {
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private class ViewHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView label;
|
||||
private final TextView sensorValue;
|
||||
private final TextView value;
|
||||
private final TextView unit;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
label = itemView.findViewById(R.id.stats_sensor_label);
|
||||
sensorValue = itemView.findViewById(R.id.stats_sensor_sensor_value);
|
||||
value = itemView.findViewById(R.id.stats_sensor_value);
|
||||
unit = itemView.findViewById(R.id.stats_sensor_unit);
|
||||
}
|
||||
|
||||
public void setData(SensorDataModel sensorDataModel) {
|
||||
String sensorName = sensorDataModel.getSensorName();
|
||||
String sensorValue = sensorDataModel.hasValue() ? sensorDataModel.getSensorValue() : context.getString(R.string.value_unknown);
|
||||
|
||||
this.label.setText(context.getString(sensorDataModel.getLabelId()));
|
||||
if (sensorName == null) {
|
||||
this.sensorValue.setVisibility(View.GONE);
|
||||
} else {
|
||||
this.sensorValue.setText(sensorName);
|
||||
}
|
||||
this.value.setText(sensorValue);
|
||||
this.unit.setText(context.getString(sensorDataModel.getUnitId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
||||
|
||||
public class SettingsCustomLayoutAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private Layout layout;
|
||||
private final Context context;
|
||||
private final SettingsCustomLayoutItemClickListener itemClickListener;
|
||||
|
||||
public SettingsCustomLayoutAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
|
||||
this.context = context;
|
||||
this.itemClickListener = itemClickListener;
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(context).inflate(R.layout.custom_stats_item, parent, false);
|
||||
return new SettingsCustomLayoutAdapter.ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
SettingsCustomLayoutAdapter.ViewHolder viewHolder = (SettingsCustomLayoutAdapter.ViewHolder) holder;
|
||||
DataField field = layout.getFields().get(position);
|
||||
viewHolder.itemView.setTag(field.getKey());
|
||||
viewHolder.title.setText(field.getTitle());
|
||||
viewHolder.value.setText(StatisticsUtils.emptyValue(context, field.getKey()));
|
||||
|
||||
viewHolder.title.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.StatsPrimaryLabel : R.style.StatsSecondaryLabel) : R.style.StatsLabelHidden);
|
||||
viewHolder.value.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.StatsPrimaryValue : R.style.StatsSecondaryValue) : R.style.StatsValueHidden);
|
||||
viewHolder.statusIcon.setVisibility(field.isVisible() ? View.GONE : View.VISIBLE);
|
||||
viewHolder.statusIcon.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_baseline_visibility_off_24));
|
||||
viewHolder.moveIcon.setVisibility(field.isVisible() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (layout == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return layout.getFields().size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return layout.getFields().get(position).isWide() ? CustomLayoutFieldType.WIDE.value() : CustomLayoutFieldType.SHORT.value();
|
||||
}
|
||||
|
||||
public boolean isItemWide(int position) {
|
||||
return getItemViewType(position) == CustomLayoutFieldType.WIDE.value();
|
||||
}
|
||||
|
||||
public DataField getItem(int position) {
|
||||
return layout.getFields().get(position);
|
||||
}
|
||||
|
||||
public void swapValues(Layout data) {
|
||||
this.layout = data;
|
||||
if (this.layout != null) {
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public Layout move(int fromPosition, int toPosition) {
|
||||
layout.moveField(fromPosition, toPosition);
|
||||
notifyItemMoved(fromPosition, toPosition);
|
||||
return layout;
|
||||
}
|
||||
|
||||
private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
final TextView title;
|
||||
final TextView value;
|
||||
final TextView unit;
|
||||
final ImageView statusIcon;
|
||||
final ImageView moveIcon;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
title = itemView.findViewById(R.id.stats_description_main);
|
||||
value = itemView.findViewById(R.id.stats_value);
|
||||
unit = itemView.findViewById(R.id.stats_unit);
|
||||
statusIcon = itemView.findViewById(R.id.stats_icon_show_status);
|
||||
moveIcon = itemView.findViewById(R.id.stats_iv_drag_indicator);
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String statTitle = (String) view.getTag();
|
||||
Optional<DataField> optionalField = layout.getFields().stream().filter(f -> f.getKey().equals(statTitle)).findFirst();
|
||||
optionalField.ifPresent(itemClickListener::onSettingsCustomLayoutItemClicked);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SettingsCustomLayoutItemClickListener {
|
||||
void onSettingsCustomLayoutItemClicked(@NonNull DataField field);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticData;
|
||||
|
||||
public class StatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private List<StatisticData> statisticDataList;
|
||||
private final Context context;
|
||||
|
||||
public StatisticsAdapter(Context context) {
|
||||
this.context = context;
|
||||
this.statisticDataList = new ArrayList<>();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_item, parent, false);
|
||||
return new StatisticsAdapter.ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
StatisticsAdapter.ViewHolder viewHolder = (StatisticsAdapter.ViewHolder) holder;
|
||||
StatisticData statisticData = statisticDataList.get(position);
|
||||
viewHolder.setData(statisticData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (statisticDataList == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return statisticDataList.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return statisticDataList.get(position).getField().isWide() ? CustomLayoutFieldType.WIDE.value() : CustomLayoutFieldType.SHORT.value();
|
||||
}
|
||||
|
||||
public boolean isItemWide(int position) {
|
||||
return getItemViewType(position) == CustomLayoutFieldType.WIDE.value();
|
||||
}
|
||||
|
||||
public List<StatisticData> swapData(List<StatisticData> data) {
|
||||
if (statisticDataList == data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
statisticDataList = data;
|
||||
|
||||
if (data != null) {
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static class WithRecordedLayout extends StatisticsAdapter {
|
||||
public WithRecordedLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_recorded_item, parent, false);
|
||||
return new StatisticsAdapter.ViewRecordedHolder(view);
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewHolder extends RecyclerView.ViewHolder {
|
||||
final TextView value;
|
||||
final TextView unit;
|
||||
final TextView descMain;
|
||||
final TextView descSecondary;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
value = itemView.findViewById(R.id.stats_value);
|
||||
unit = itemView.findViewById(R.id.stats_unit);
|
||||
descMain = itemView.findViewById(R.id.stats_description_main);
|
||||
descSecondary = itemView.findViewById(R.id.stats_description_secondary);
|
||||
}
|
||||
|
||||
public void setData(StatisticData statisticData) {
|
||||
if (statisticData == null) {
|
||||
return;
|
||||
}
|
||||
this.value.setText(statisticData.hasValue() ? statisticData.getValue() : context.getString(R.string.value_unknown));
|
||||
this.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.StatsPrimaryValue : R.style.StatsSecondaryValue);
|
||||
this.unit.setText(statisticData.getUnit());
|
||||
this.descMain.setText(statisticData.getField().getTitle());
|
||||
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.StatsPrimaryLabel : R.style.StatsSecondaryLabel);
|
||||
if (statisticData.hasDescription()) {
|
||||
this.descSecondary.setVisibility(View.VISIBLE);
|
||||
this.descSecondary.setText(statisticData.getDescription());
|
||||
} else {
|
||||
this.descSecondary.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewRecordedHolder extends ViewHolder {
|
||||
|
||||
public ViewRecordedHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public void setData(StatisticData statisticData) {
|
||||
if (statisticData == null) {
|
||||
return;
|
||||
}
|
||||
this.value.setText(statisticData.hasValue() ? statisticData.getValue() : context.getString(R.string.value_unknown));
|
||||
this.unit.setText(statisticData.getUnit());
|
||||
this.descMain.setText(statisticData.getField().getTitle());
|
||||
if (statisticData.hasDescription()) {
|
||||
this.descSecondary.setVisibility(View.VISIBLE);
|
||||
this.descSecondary.setText(statisticData.getDescription());
|
||||
} else {
|
||||
this.descSecondary.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.util.CsvConstants;
|
||||
|
||||
public class DataField {
|
||||
public static final String YES_VALUE = "1";
|
||||
public static final String NOT_VALUE = "0";
|
||||
|
||||
private final String key;
|
||||
private final String title;
|
||||
private boolean isVisible;
|
||||
private boolean isPrimary;
|
||||
private final boolean isWide;
|
||||
|
||||
public DataField(String key, String title, boolean isVisible, boolean isPrimary, boolean isWide) {
|
||||
this.key = key;
|
||||
this.title = title;
|
||||
this.isVisible = isVisible;
|
||||
this.isPrimary = isPrimary;
|
||||
this.isWide = isWide;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
public void toggleVisibility() {
|
||||
isVisible = !isVisible;
|
||||
}
|
||||
|
||||
public boolean isPrimary() {
|
||||
return isPrimary;
|
||||
}
|
||||
|
||||
public void togglePrimary() {
|
||||
isPrimary = !isPrimary;
|
||||
}
|
||||
|
||||
public boolean isWide() {
|
||||
return isWide;
|
||||
}
|
||||
|
||||
public String toCsv() {
|
||||
String visible = this.isVisible ? YES_VALUE : NOT_VALUE;
|
||||
String primary = this.isPrimary ? YES_VALUE : NOT_VALUE;
|
||||
return key + CsvConstants.ITEM_SEPARATOR + visible + CsvConstants.ITEM_SEPARATOR + primary;
|
||||
}
|
||||
|
||||
public static String getTitleByKey(Context context, String key) {
|
||||
if (key.equals(context.getString(R.string.stats_custom_layout_total_time_key))) {
|
||||
return context.getString(R.string.stats_total_time);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_moving_time_key))) {
|
||||
return context.getString(R.string.stats_moving_time);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_distance_key))) {
|
||||
return context.getString(R.string.stats_distance);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_speed_key))) {
|
||||
return context.getString(R.string.stats_speed);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_pace_key))) {
|
||||
return context.getString(R.string.stats_pace);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
|
||||
return context.getString(R.string.stats_average_moving_speed);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_speed_key))) {
|
||||
return context.getString(R.string.stats_average_speed);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_max_speed_key))) {
|
||||
return context.getString(R.string.stats_max_speed);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
|
||||
return context.getString(R.string.stats_average_moving_pace);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_pace_key))) {
|
||||
return context.getString(R.string.stats_average_pace);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_fastest_pace_key))) {
|
||||
return context.getString(R.string.stats_fastest_pace);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_altitude_key))) {
|
||||
return context.getString(R.string.stats_altitude);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_gain_key))) {
|
||||
return context.getString(R.string.stats_gain);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_loss_key))) {
|
||||
return context.getString(R.string.stats_loss);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_coordinates_key))) {
|
||||
return context.getString(R.string.stats_coordinates);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_heart_rate_key))) {
|
||||
return context.getString(R.string.stats_sensors_heart_rate);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_cadence_key))) {
|
||||
return context.getString(R.string.stats_sensors_cadence);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_power_key))) {
|
||||
return context.getString(R.string.stats_sensors_power);
|
||||
} else {
|
||||
throw new RuntimeException("It doesn't exists a field with key: " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Layout {
|
||||
private final String profile;
|
||||
private final List<DataField> dataFields = new ArrayList<>();
|
||||
|
||||
public Layout(String profile) {
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public void addField(String key, String title, boolean visible, boolean primary, boolean isWide) {
|
||||
dataFields.add(new DataField(key, title, visible, primary, isWide));
|
||||
}
|
||||
|
||||
public void addField(DataField dataField) {
|
||||
dataFields.add(dataField);
|
||||
}
|
||||
|
||||
public void addFields(List<DataField> dataFields) {
|
||||
this.dataFields.addAll(dataFields);
|
||||
}
|
||||
|
||||
public void removeField(DataField dataField) {
|
||||
dataFields.remove(dataField);
|
||||
}
|
||||
|
||||
public List<DataField> getFields() {
|
||||
return new ArrayList<>(dataFields);
|
||||
}
|
||||
|
||||
public void moveField(int from, int to) {
|
||||
DataField dataFieldToMove = dataFields.remove(from);
|
||||
dataFields.add(to, dataFieldToMove);
|
||||
}
|
||||
|
||||
public String getProfile() {
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
@@ -290,6 +290,31 @@ public class ContentProviderUtils {
|
||||
return values;
|
||||
}
|
||||
|
||||
public void updateTrackStatistics(@NonNull Track.Id trackId, @NonNull TrackStatistics trackStatistics) {
|
||||
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(trackStatistics), TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())});
|
||||
}
|
||||
|
||||
private ContentValues createContentValues(TrackStatistics trackStatistics) {
|
||||
ContentValues values = new ContentValues();
|
||||
if (trackStatistics.getStartTime() != null) {
|
||||
values.put(TracksColumns.STARTTIME, trackStatistics.getStartTime().toEpochMilli());
|
||||
}
|
||||
if (trackStatistics.getStopTime() != null) {
|
||||
values.put(TracksColumns.STOPTIME, trackStatistics.getStopTime().toEpochMilli());
|
||||
}
|
||||
values.put(TracksColumns.TOTALDISTANCE, trackStatistics.getTotalDistance().toM());
|
||||
values.put(TracksColumns.TOTALTIME, trackStatistics.getTotalTime().toMillis());
|
||||
values.put(TracksColumns.MOVINGTIME, trackStatistics.getMovingTime().toMillis());
|
||||
values.put(TracksColumns.AVGSPEED, trackStatistics.getAverageSpeed().toMPS());
|
||||
values.put(TracksColumns.AVGMOVINGSPEED, trackStatistics.getAverageMovingSpeed().toMPS());
|
||||
values.put(TracksColumns.MAXSPEED, trackStatistics.getMaxSpeed().toMPS());
|
||||
values.put(TracksColumns.MIN_ALTITUDE, trackStatistics.getMinAltitude());
|
||||
values.put(TracksColumns.MAX_ALTITUDE, trackStatistics.getMaxAltitude());
|
||||
values.put(TracksColumns.ALTITUDE_GAIN, trackStatistics.getTotalAltitudeGain());
|
||||
values.put(TracksColumns.ALTITUDE_LOSS, trackStatistics.getTotalAltitudeLoss());
|
||||
return values;
|
||||
}
|
||||
|
||||
public Marker createMarker(Cursor cursor) {
|
||||
int idIndex = cursor.getColumnIndexOrThrow(MarkerColumns._ID);
|
||||
int nameIndex = cursor.getColumnIndexOrThrow(MarkerColumns.NAME);
|
||||
|
||||
@@ -254,9 +254,9 @@ public class MarkerDetailFragment extends Fragment {
|
||||
|
||||
private void setLocation(boolean addShadow) {
|
||||
String value = "[" + getString(R.string.stats_latitude) + " "
|
||||
+ StringUtils.formatCoordinate(marker.getLatitude()) + ", "
|
||||
+ StringUtils.formatCoordinate(getContext(), marker.getLatitude()) + ", "
|
||||
+ getString(R.string.stats_longitude) + " "
|
||||
+ StringUtils.formatCoordinate(marker.getLongitude()) + "]";
|
||||
+ StringUtils.formatCoordinate(getContext(), marker.getLongitude()) + "]";
|
||||
|
||||
ListItemUtils.setTextView(getActivity(), viewBinding.markerDetailMarkerLocation, value, addShadow);
|
||||
}
|
||||
|
||||
@@ -31,12 +31,11 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||
import de.dennisguse.opentracks.adapters.SensorsAdapter;
|
||||
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.databinding.StatisticsRecordedBinding;
|
||||
@@ -45,7 +44,8 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticData;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticDataBuilder;
|
||||
|
||||
/**
|
||||
* A fragment to display track statistics to the user for a recorded {@link Track}.
|
||||
@@ -69,7 +69,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
}
|
||||
|
||||
private SensorStatistics sensorStatistics;
|
||||
private SensorsAdapter sensorsAdapter;
|
||||
private StatisticsAdapter.WithRecordedLayout sensorsAdapter;
|
||||
|
||||
private Track.Id trackId;
|
||||
@Nullable // Lazily loaded.
|
||||
@@ -114,7 +114,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
||||
|
||||
sensorsAdapter = new SensorsAdapter(getContext());
|
||||
sensorsAdapter = new StatisticsAdapter.WithRecordedLayout(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -238,10 +238,6 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
// Set altitude gain and loss
|
||||
{
|
||||
// Make altitude visible?
|
||||
boolean show = PreferencesUtils.isShowStatsAltitude(sharedPreferences, getContext());
|
||||
viewBinding.statsAltitudeGroup.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
|
||||
Float altitudeGain_m = trackStatistics.getTotalAltitudeGain();
|
||||
Float altitudeLoss_m = trackStatistics.getTotalAltitudeLoss();
|
||||
|
||||
@@ -254,6 +250,9 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
parts = StringUtils.getAltitudeParts(getContext(), altitudeLoss_m, preferenceMetricUnits);
|
||||
viewBinding.statsAltitudeLossValue.setText(parts.first);
|
||||
viewBinding.statsAltitudeLossUnit.setText(parts.second);
|
||||
|
||||
boolean show = altitudeGain_m != null && altitudeLoss_m != null;
|
||||
viewBinding.statsAltitudeGroup.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,18 +261,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
return;
|
||||
}
|
||||
|
||||
List<SensorDataModel> sensorDataList = new ArrayList<>();
|
||||
if (sensorStatistics.hasHeartRate()) {
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_heart_rate_max, R.string.sensor_unit_beats_per_minute, sensorStatistics.getMaxHeartRate()));
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_heart_rate_avg, R.string.sensor_unit_beats_per_minute, sensorStatistics.getAvgHeartRate()));
|
||||
}
|
||||
if (sensorStatistics.hasCadence()) {
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_cadence_max, R.string.sensor_unit_rounds_per_minute, sensorStatistics.getMaxCadence()));
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_cadence_avg, R.string.sensor_unit_rounds_per_minute, sensorStatistics.getAvgCadence()));
|
||||
}
|
||||
if (sensorStatistics.hasPower()) {
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_power_avg, R.string.sensor_unit_power, sensorStatistics.getAvgPower()));
|
||||
}
|
||||
List<StatisticData> sensorDataList = StatisticDataBuilder.fromSensorStatistics(getContext(), sensorStatistics);
|
||||
if (sensorDataList.size() > 0) {
|
||||
sensorsAdapter.swapData(sensorDataList);
|
||||
}
|
||||
|
||||
+39
-178
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.fragments;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -11,28 +10,25 @@ import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackRecordingActivity;
|
||||
import de.dennisguse.opentracks.adapters.SensorsAdapter;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticData;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticsDataModel;
|
||||
|
||||
/**
|
||||
* A fragment to display track statistics to the user for a currently recording {@link Track}.
|
||||
@@ -51,15 +47,16 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private TrackRecordingService.RecordingData recordingData = TrackRecordingService.NOT_RECORDING;
|
||||
private TrackPoint latestTrackPoint;
|
||||
private Layout layout;
|
||||
|
||||
private StatisticsRecordingBinding viewBinding;
|
||||
private SensorsAdapter sensorsAdapter;
|
||||
private StatisticsAdapter statisticsAdapter;
|
||||
private GridLayoutManager gridLayoutManager;
|
||||
private StatisticsDataModel viewModel;
|
||||
private LiveData<List<StatisticData>> statisticsLiveData;
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
private boolean preferenceMetricUnits;
|
||||
private boolean preferenceReportSpeed;
|
||||
private boolean preferenceShowAltitude;
|
||||
private boolean preferenceShowCoordinate;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
boolean updateUInecessary = false;
|
||||
@@ -69,19 +66,13 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
preferenceMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key) && recordingData != null) {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_custom_layout_fields_key, key)) {
|
||||
updateUInecessary = true;
|
||||
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), recordingData.getTrackCategory());
|
||||
layout = PreferencesUtils.getCustomLayout(sharedPreferences, getContext());
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_show_grade_altitude_key, key)) {
|
||||
updateUInecessary = true;
|
||||
preferenceShowAltitude = PreferencesUtils.isShowStatsAltitude(sharedPreferences, getContext());
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_show_coordinate_key, key)) {
|
||||
updateUInecessary = true;
|
||||
preferenceShowCoordinate = PreferencesUtils.isStatsShowCoordinate(sharedPreferences, getContext());
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_custom_layout_columns_key, key)) {
|
||||
gridLayoutManager.setSpanCount(PreferencesUtils.getLayoutColumns(sharedPreferences, getContext()));
|
||||
}
|
||||
|
||||
if (key != null && updateUInecessary && isResumed()) {
|
||||
@@ -109,17 +100,28 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
|
||||
sensorsAdapter = new SensorsAdapter(getContext());
|
||||
statisticsAdapter = new StatisticsAdapter(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
viewBinding = StatisticsRecordingBinding.inflate(inflater, container, false);
|
||||
viewBinding.statsActivityTypeIcon.setOnClickListener(v -> ((TrackRecordingActivity) getActivity()).chooseActivityType(recordingData.getTrackCategory()));
|
||||
|
||||
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
|
||||
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
||||
sensorsRecyclerView.setAdapter(sensorsAdapter);
|
||||
RecyclerView recyclerView = viewBinding.statsRecyclerView;
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), RecyclerView.VERTICAL));
|
||||
final int numColumns = PreferencesUtils.getLayoutColumns(sharedPreferences, getContext());
|
||||
gridLayoutManager = new GridLayoutManager(getContext(), numColumns);
|
||||
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
||||
@Override
|
||||
public int getSpanSize(int position) {
|
||||
if (statisticsAdapter.isItemWide(position)) {
|
||||
return numColumns;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
recyclerView.setLayoutManager(gridLayoutManager);
|
||||
recyclerView.setAdapter(statisticsAdapter);
|
||||
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
@@ -131,13 +133,16 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
viewModel = new ViewModelProvider(getActivity()).get(StatisticsDataModel.class);
|
||||
statisticsLiveData = viewModel.getStatsData();
|
||||
statisticsLiveData.observe(getActivity(), statsDataList -> statisticsAdapter.swapData(statsDataList));
|
||||
|
||||
trackRecordingServiceConnection.startConnection(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
@@ -158,160 +163,16 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
super.onDestroy();
|
||||
trackRecordingServiceConnection = null;
|
||||
sharedPreferences = null;
|
||||
viewModel = null;
|
||||
statisticsLiveData.removeObservers(getActivity());
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
if (isResumed()) {
|
||||
updateStats();
|
||||
setLocationValues();
|
||||
updateSensorDataUI();
|
||||
viewModel.update(recordingData, layout, preferenceMetricUnits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to fetch most recent {@link SensorDataSet} from {@link de.dennisguse.opentracks.services.TrackRecordingService}.
|
||||
* Also sets altitude gain and loss.
|
||||
*/
|
||||
private void updateSensorDataUI() {
|
||||
SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
|
||||
if (sensorDataSet != null) {
|
||||
List<SensorDataModel> sensorDataList = new ArrayList<>();
|
||||
if (sensorDataSet.getHeartRate() != null) {
|
||||
sensorDataList.add(new SensorDataModel(sensorDataSet.getHeartRate()));
|
||||
}
|
||||
if (sensorDataSet.getCyclingCadence() != null) {
|
||||
sensorDataList.add(new SensorDataModel(sensorDataSet.getCyclingCadence()));
|
||||
}
|
||||
if (sensorDataSet.getCyclingPower() != null) {
|
||||
sensorDataList.add(new SensorDataModel(sensorDataSet.getCyclingPower()));
|
||||
}
|
||||
sensorsAdapter.swapData(sensorDataList);
|
||||
}
|
||||
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getAltitudeParts(getContext(), recordingData.getTrackStatistics().getTotalAltitudeGain(), preferenceMetricUnits);
|
||||
viewBinding.statsAltitudeGainValue.setText(parts.first);
|
||||
viewBinding.statsAltitudeGainUnit.setText(parts.second);
|
||||
}
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getAltitudeParts(getContext(), recordingData.getTrackStatistics().getTotalAltitudeLoss(), preferenceMetricUnits);
|
||||
viewBinding.statsAltitudeLossValue.setText(parts.first);
|
||||
viewBinding.statsAltitudeLossUnit.setText(parts.second);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStats() {
|
||||
// Set activity type
|
||||
String trackIconValue = TrackIconUtils.getIconValue(getContext(), recordingData.getTrackCategory());
|
||||
viewBinding.statsActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(trackIconValue));
|
||||
|
||||
// Set time
|
||||
viewBinding.statsMovingTimeValue.setText(StringUtils.formatElapsedTime(recordingData.getTrackStatistics().getMovingTime()));
|
||||
updateTotalTime();
|
||||
|
||||
// Set average speed/pace
|
||||
{
|
||||
Speed speed = recordingData.getTrackStatistics().getAverageSpeed();
|
||||
viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsAverageSpeedValue.setText(parts.first);
|
||||
viewBinding.statsAverageSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set max speed/pace
|
||||
{
|
||||
Speed speed = recordingData.getTrackStatistics().getMaxSpeed();
|
||||
|
||||
viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsMaxSpeedValue.setText(parts.first);
|
||||
viewBinding.statsMaxSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set moving speed/pace
|
||||
{
|
||||
Speed speed = recordingData.getTrackStatistics().getAverageMovingSpeed();
|
||||
|
||||
viewBinding.statsMovingSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsMovingSpeedValue.setText(parts.first);
|
||||
viewBinding.statsMovingSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set altitude gain and loss
|
||||
viewBinding.statsAltitudeGroup.setVisibility(preferenceShowAltitude ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void updateTotalTime() {
|
||||
Duration totalTime = recordingData.getTrackStatistics().getTotalTime();
|
||||
viewBinding.statsTotalTimeValue.setText(StringUtils.formatElapsedTime(totalTime));
|
||||
}
|
||||
|
||||
private void setLocationValues() {
|
||||
// Set speed/pace
|
||||
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
|
||||
|
||||
SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingDistanceSpeed() != null) {
|
||||
SensorDataCycling.DistanceSpeed data = sensorDataSet.getCyclingDistanceSpeed();
|
||||
if (data.hasValue() && data.isRecent()) {
|
||||
speed = data.getValue().getSpeed();
|
||||
}
|
||||
}
|
||||
|
||||
setSpeed(speed);
|
||||
|
||||
// Set distance
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), recordingData.getTrackStatistics().getTotalDistance(), preferenceMetricUnits);
|
||||
|
||||
viewBinding.statsDistanceValue.setText(parts.first);
|
||||
viewBinding.statsDistanceUnit.setText(parts.second);
|
||||
}
|
||||
// Set altitude
|
||||
viewBinding.statsAltitudeGroup.setVisibility(preferenceShowAltitude ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (preferenceShowAltitude) {
|
||||
// Current altitude
|
||||
Float altitude = null;
|
||||
int labelId = R.string.value_unknown;
|
||||
if (latestTrackPoint != null && latestTrackPoint.hasAltitude()) {
|
||||
altitude = (float) latestTrackPoint.getAltitude().toM();
|
||||
labelId = latestTrackPoint.getAltitude().getLabelId();
|
||||
}
|
||||
|
||||
Pair<String, String> parts = StringUtils.getAltitudeParts(getContext(), altitude, preferenceMetricUnits);
|
||||
viewBinding.statsAltitudeCurrentValue.setText(parts.first);
|
||||
viewBinding.statsAltitudeCurrentUnit.setText(parts.second);
|
||||
|
||||
viewBinding.statsAltitudeCurrentLabelEgm.setText(labelId);
|
||||
}
|
||||
|
||||
// Set coordinate
|
||||
viewBinding.statsCoordinateGroup.setVisibility(preferenceShowCoordinate ? View.VISIBLE : View.GONE);
|
||||
if (preferenceShowCoordinate) {
|
||||
String latitudeText = getContext().getString(R.string.value_unknown);
|
||||
String longitudeText = getContext().getString(R.string.value_unknown);
|
||||
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) {
|
||||
latitudeText = StringUtils.formatCoordinate(latestTrackPoint.getLatitude());
|
||||
longitudeText = StringUtils.formatCoordinate(latestTrackPoint.getLongitude());
|
||||
}
|
||||
viewBinding.statsLatitudeValue.setText(latitudeText);
|
||||
viewBinding.statsLongitudeValue.setText(longitudeText);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSpeed(Speed speed) {
|
||||
viewBinding.statsSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_speed : R.string.stats_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsSpeedValue.setText(parts.first);
|
||||
viewBinding.statsSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
private void onRecordingDataChanged(TrackRecordingService.RecordingData recordingData) {
|
||||
String oldCategory = this.recordingData.getTrackCategory();
|
||||
String newCategory = recordingData.getTrackCategory();
|
||||
|
||||
@@ -274,7 +274,7 @@ class TrackRecordingManager {
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||
|
||||
contentProviderUtils.updateTrack(track);
|
||||
contentProviderUtils.updateTrackStatistics(track.getId(), track.getTrackStatistics());
|
||||
} catch (SQLiteException e) {
|
||||
/*
|
||||
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.settings;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
@@ -35,15 +36,23 @@ public class ResetDialogPreference extends DialogPreference {
|
||||
|
||||
@Override
|
||||
public void onDialogClosed(boolean positiveResult) {
|
||||
if (positiveResult) {
|
||||
FragmentActivity activity = getActivity();
|
||||
if (!positiveResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
FragmentActivity activity = getActivity();
|
||||
|
||||
String preferenceKey = getArguments().getString(PreferenceDialogFragmentCompat.ARG_KEY);
|
||||
if (preferenceKey.equals(getString(R.string.settings_reset_key))) {
|
||||
PreferencesUtils.resetPreferences(activity, true);
|
||||
Toast.makeText(activity, R.string.settings_reset_done, Toast.LENGTH_SHORT).show();
|
||||
} else if (preferenceKey.equals(getString(R.string.settings_layout_reset_key))) {
|
||||
PreferencesUtils.resetCustomLayoutPreferences(activity);
|
||||
Toast.makeText(activity, R.string.settings_layout_reset_done, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
if (activity instanceof ResetCallback) {
|
||||
((ResetCallback) activity).onReset();
|
||||
}
|
||||
if (activity instanceof ResetCallback) {
|
||||
((ResetCallback) activity).onReset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +208,13 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
}
|
||||
);
|
||||
|
||||
Preference customLayoutPreference = findPreference(getString(R.string.stats_custom_layout_key));
|
||||
customLayoutPreference.setOnPreferenceClickListener((preference) -> {
|
||||
Intent intent = new Intent(getContext(), SettingsCustomLayoutActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
});
|
||||
|
||||
announcementsFrequency = findPreference(getString(R.string.voice_frequency_key));
|
||||
announcementsSpeed = findPreference(getString(R.string.voice_speed_rate_key));
|
||||
updateVoiceAnnouncements();
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
package de.dennisguse.opentracks.settings;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import de.dennisguse.opentracks.AbstractActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.adapters.SettingsCustomLayoutAdapter;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutBinding;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
||||
|
||||
public class SettingsCustomLayoutActivity extends AbstractActivity implements SettingsCustomLayoutAdapter.SettingsCustomLayoutItemClickListener {
|
||||
|
||||
private ActivitySettingsCustomLayoutBinding viewBinding;
|
||||
private GridLayoutManager gridLayoutManager;
|
||||
private SettingsCustomLayoutAdapter adapterFieldsVisible;
|
||||
private SettingsCustomLayoutAdapter adapterFieldsHidden;
|
||||
private Layout layoutFieldsVisible;
|
||||
private Layout layoutFieldsHidden;
|
||||
private SharedPreferences sharedPreferences;
|
||||
private int numColumns;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
|
||||
// Recycler view with visible stats.
|
||||
layoutFieldsVisible = StatisticsUtils.filterVisible(PreferencesUtils.getCustomLayout(sharedPreferences, this), true);
|
||||
adapterFieldsVisible = new SettingsCustomLayoutAdapter(this, this, layoutFieldsVisible);
|
||||
|
||||
numColumns = PreferencesUtils.getLayoutColumns(sharedPreferences, this);
|
||||
RecyclerView recyclerViewVisible = viewBinding.recyclerViewVisible;
|
||||
gridLayoutManager = new GridLayoutManager(this, numColumns);
|
||||
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
||||
@Override
|
||||
public int getSpanSize(int position) {
|
||||
if (adapterFieldsVisible.isItemWide(position)) {
|
||||
return numColumns;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
recyclerViewVisible.setLayoutManager(gridLayoutManager);
|
||||
recyclerViewVisible.setAdapter(adapterFieldsVisible);
|
||||
|
||||
ItemTouchHelper.SimpleCallback simpleCallback = new ItemTouchHelper.SimpleCallback(
|
||||
ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.START | ItemTouchHelper.END, 0) {
|
||||
@Override
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||
int fromPosition = viewHolder.getAdapterPosition();
|
||||
int toPosition = target.getAdapterPosition();
|
||||
layoutFieldsVisible = adapterFieldsVisible.move(fromPosition, toPosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
||||
}
|
||||
};
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleCallback);
|
||||
itemTouchHelper.attachToRecyclerView(recyclerViewVisible);
|
||||
|
||||
// Spinner with items per row.
|
||||
ArrayAdapter<Integer> spinnerAdapter = new ArrayAdapter<>(this,
|
||||
android.R.layout.simple_spinner_dropdown_item,
|
||||
IntStream.of(getResources().getIntArray(R.array.stats_custom_layout_fields_columns_per_row)).boxed().toArray(Integer[]::new));
|
||||
viewBinding.spinnerOptions.setAdapter(spinnerAdapter);
|
||||
viewBinding.spinnerOptions.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
numColumns = position + 1;
|
||||
gridLayoutManager.setSpanCount(numColumns);
|
||||
PreferencesUtils.setLayoutColumns(sharedPreferences, SettingsCustomLayoutActivity.this, position + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
|
||||
}
|
||||
});
|
||||
viewBinding.spinnerOptions.setSelection(PreferencesUtils.getLayoutColumns(sharedPreferences, SettingsCustomLayoutActivity.this) - 1);
|
||||
|
||||
// Recycler view with not visible stats.
|
||||
layoutFieldsHidden = StatisticsUtils.filterVisible(PreferencesUtils.getCustomLayout(sharedPreferences, this), false);
|
||||
adapterFieldsHidden = new SettingsCustomLayoutAdapter(this, this, layoutFieldsHidden);
|
||||
RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible;
|
||||
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
|
||||
recyclerViewNotVisible.setAdapter(adapterFieldsHidden);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (!layoutFieldsVisible.getFields().isEmpty() || !layoutFieldsHidden.getFields().isEmpty()) {
|
||||
Layout newLayout = new Layout(layoutFieldsVisible.getProfile());
|
||||
newLayout.addFields(layoutFieldsVisible.getFields());
|
||||
newLayout.addFields(layoutFieldsHidden.getFields());
|
||||
PreferencesUtils.setCustomLayout(sharedPreferences, this, newLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
sharedPreferences = null;
|
||||
layoutFieldsVisible = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupActionBarBack(Toolbar toolbar) {
|
||||
super.setupActionBarBack(toolbar);
|
||||
toolbar.setTitle(R.string.menu_settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
viewBinding = ActivitySettingsCustomLayoutBinding.inflate(getLayoutInflater());
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSettingsCustomLayoutItemClicked(@NonNull DataField field) {
|
||||
if (field.isVisible()) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.generic_choose_an_option)
|
||||
.setItems(new String[]{getString(field.isPrimary() ? R.string.field_set_secondary : R.string.field_set_primary), getString(R.string.field_remove_from_layout)}, (dialog, which) -> {
|
||||
if (which == 0) {
|
||||
field.togglePrimary();
|
||||
} else {
|
||||
layoutFieldsVisible.removeField(field);
|
||||
field.toggleVisibility();
|
||||
layoutFieldsHidden.addField(field);
|
||||
}
|
||||
|
||||
adapterFieldsVisible.swapValues(layoutFieldsVisible);
|
||||
adapterFieldsHidden.swapValues(layoutFieldsHidden);
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
} else {
|
||||
layoutFieldsHidden.removeField(field);
|
||||
field.toggleVisibility();
|
||||
layoutFieldsVisible.addField(field);
|
||||
viewBinding.scrollView.fullScroll(ScrollView.FOCUS_UP);
|
||||
|
||||
adapterFieldsVisible.swapValues(layoutFieldsVisible);
|
||||
adapterFieldsHidden.swapValues(layoutFieldsHidden);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
public final class CsvConstants {
|
||||
private CsvConstants() {
|
||||
|
||||
}
|
||||
|
||||
public static final String LINE_SEPARATOR = ";";
|
||||
public static final String ITEM_SEPARATOR = ",";
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
public class PreferencesOpenHelper {
|
||||
private final Context context;
|
||||
private final SharedPreferences sharedPreferences;
|
||||
|
||||
private static final int PREFERENCES_VERSION = 1;
|
||||
|
||||
private PreferencesOpenHelper(Context context, SharedPreferences sharedPreferences) {
|
||||
this.context = context;
|
||||
this.sharedPreferences = sharedPreferences;
|
||||
}
|
||||
|
||||
public static PreferencesOpenHelper newInstance(Context context, SharedPreferences sharedPreferences) {
|
||||
return new PreferencesOpenHelper(context, sharedPreferences);
|
||||
}
|
||||
|
||||
public void checkForUpgrade() {
|
||||
int lastVersion = PreferencesUtils.getInt(sharedPreferences, context, R.string.prefs_last_version_key, 0);
|
||||
if (PREFERENCES_VERSION > lastVersion) {
|
||||
onUpgrade();
|
||||
}
|
||||
}
|
||||
|
||||
private void onUpgrade() {
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.prefs_last_version_key, PREFERENCES_VERSION);
|
||||
switch (PREFERENCES_VERSION) {
|
||||
case 1:
|
||||
upgradeFrom0to1();
|
||||
}
|
||||
}
|
||||
|
||||
private void upgradeFrom0to1() {
|
||||
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_custom_layout_fields_key, PreferencesUtils.buildDefaultLayout(context));
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,11 @@
|
||||
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.content.res.TypedArray;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -26,8 +28,16 @@ import androidx.annotation.VisibleForTesting;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
|
||||
/**
|
||||
@@ -44,7 +54,9 @@ public class PreferencesUtils {
|
||||
|
||||
@Deprecated //Should only be used to get a sharedPreference for more than one interaction!
|
||||
public static SharedPreferences getSharedPreferences(Context context) {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
PreferencesOpenHelper.newInstance(context, sharedPreferences).checkForUpgrade();
|
||||
return sharedPreferences;
|
||||
}
|
||||
|
||||
public static String getDefaultActivity(SharedPreferences sharedPreferences, Context context) {
|
||||
@@ -80,7 +92,7 @@ public class PreferencesUtils {
|
||||
return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue);
|
||||
}
|
||||
|
||||
private static int getInt(SharedPreferences sharedPreferences, Context context, int keyId, int defaultValue) {
|
||||
static int getInt(SharedPreferences sharedPreferences, Context context, int keyId, int defaultValue) {
|
||||
try {
|
||||
return sharedPreferences.getInt(getKey(context, keyId), defaultValue);
|
||||
} catch (ClassCastException e) {
|
||||
@@ -123,6 +135,12 @@ public class PreferencesUtils {
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
static void setInt(SharedPreferences sharedPreferences, Context context, int keyId, int value) {
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putInt(getKey(context, keyId), value);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static boolean isMetricUnits(SharedPreferences sharedPreferences, Context context) {
|
||||
final String STATS_UNIT = context.getString(R.string.stats_units_default);
|
||||
return STATS_UNIT.equals(getString(sharedPreferences, context, R.string.stats_units_key, STATS_UNIT));
|
||||
@@ -196,16 +214,6 @@ public class PreferencesUtils {
|
||||
return getBoolean(sharedPreferences, context, R.string.stats_fullscreen_while_recording_key, DEFAULT);
|
||||
}
|
||||
|
||||
public static boolean isShowStatsAltitude(SharedPreferences sharedPreferences, Context context) {
|
||||
final boolean STATS_SHOW_ALTITUDE = context.getResources().getBoolean(R.bool.stats_show_altitude_default);
|
||||
return getBoolean(sharedPreferences, context, R.string.stats_show_grade_altitude_key, STATS_SHOW_ALTITUDE);
|
||||
}
|
||||
|
||||
public static boolean isStatsShowCoordinate(SharedPreferences sharedPreferences, Context context) {
|
||||
final boolean STATS_SHOW_COORDINATE = context.getResources().getBoolean(R.bool.stats_show_coordinate_default);
|
||||
return getBoolean(sharedPreferences, context, R.string.stats_show_coordinate_key, STATS_SHOW_COORDINATE);
|
||||
}
|
||||
|
||||
public static int getVoiceFrequency(SharedPreferences sharedPreferences, Context context) {
|
||||
final int VOICE_FREQUENCY_DEFAULT = Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default));
|
||||
return getInt(sharedPreferences, context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT);
|
||||
@@ -318,4 +326,78 @@ public class PreferencesUtils {
|
||||
public static boolean isDefaultExportDirectoryUri(SharedPreferences sharedPreferences, Context context) {
|
||||
return getDefaultExportDirectoryUri(sharedPreferences, context) != null;
|
||||
}
|
||||
|
||||
public static int getLayoutColumns(SharedPreferences sharedPreferences, Context context) {
|
||||
return getInt(sharedPreferences, context, R.string.stats_custom_layout_columns_key, context.getResources().getInteger(R.integer.stats_custom_layout_columns_default));
|
||||
}
|
||||
|
||||
public static void setLayoutColumns(SharedPreferences sharedPreferences, Context context, int columns) {
|
||||
setInt(sharedPreferences, context, R.string.stats_custom_layout_columns_key, columns);
|
||||
}
|
||||
|
||||
private static List<TypedArray> getMultiTypedArray(Context context, String key) {
|
||||
List<TypedArray> typedArrays = new ArrayList<>();
|
||||
|
||||
try {
|
||||
Class<R.array> resource = R.array.class;
|
||||
Field field;
|
||||
int i = 0;
|
||||
|
||||
do {
|
||||
field = resource.getField(key + context.getString(R.string.stats_custom_layout_fields_default_value_separator) + i);
|
||||
typedArrays.add(context.getResources().obtainTypedArray(field.getInt(null)));
|
||||
i++;
|
||||
} while (field != null);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, e.getMessage());
|
||||
}
|
||||
|
||||
return typedArrays;
|
||||
}
|
||||
|
||||
@SuppressLint("ResourceType")
|
||||
static String buildDefaultLayout(Context context) {
|
||||
List<TypedArray> fieldsArrays = getMultiTypedArray(context, "stats_custom_layout_fields_default_value");
|
||||
return context.getString(R.string.default_activity_default) + CsvConstants.LINE_SEPARATOR
|
||||
+ fieldsArrays.stream().map(i -> i.getString(0) + CsvConstants.ITEM_SEPARATOR + i.getString(1)).collect(Collectors.joining(CsvConstants.LINE_SEPARATOR))
|
||||
+ CsvConstants.LINE_SEPARATOR;
|
||||
}
|
||||
|
||||
public static Layout getCustomLayout(SharedPreferences sharedPreferences, Context context) {
|
||||
String csvCustomLayout = getString(sharedPreferences, context, R.string.stats_custom_layout_fields_key, buildDefaultLayout(context));
|
||||
List<String> csvParts = Arrays.asList(csvCustomLayout.split(CsvConstants.LINE_SEPARATOR));
|
||||
Layout layout = new Layout(csvParts.get(0));
|
||||
for (int i = 1; i < csvParts.size(); i++) {
|
||||
String[] fieldParts = csvParts.get(i).split(CsvConstants.ITEM_SEPARATOR);
|
||||
layout.addField(fieldParts[0], DataField.getTitleByKey(context, fieldParts[0]), fieldParts[1].equals(DataField.YES_VALUE), fieldParts[2].equals(DataField.YES_VALUE), fieldParts[0].equals(context.getString(R.string.stats_custom_layout_coordinates_key)));
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
public static void setCustomLayout(SharedPreferences sharedPreferences, Context context, Layout layout) {
|
||||
List<DataField> fields = layout.getFields();
|
||||
if (fields.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String csv = layout.getProfile() + CsvConstants.LINE_SEPARATOR
|
||||
+ fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvConstants.LINE_SEPARATOR)) + CsvConstants.LINE_SEPARATOR;
|
||||
setString(sharedPreferences, context, R.string.stats_custom_layout_fields_key, csv);
|
||||
}
|
||||
|
||||
public static void resetCustomLayoutPreferences(Context context) {
|
||||
SharedPreferences settings = getSharedPreferences(context);
|
||||
if (settings.contains(context.getString(R.string.stats_custom_layout_fields_key))) {
|
||||
SharedPreferences.Editor editor = settings.edit();
|
||||
editor.remove(context.getString(R.string.stats_custom_layout_fields_key));
|
||||
editor.commit();
|
||||
}
|
||||
if (settings.contains(context.getString(R.string.stats_custom_layout_columns_key))) {
|
||||
SharedPreferences.Editor editor = settings.edit();
|
||||
editor.remove(context.getString(R.string.stats_custom_layout_columns_key));
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
|
||||
public class StatisticsUtils {
|
||||
|
||||
public static String emptyValue(@NonNull Context context, @NonNull String statTitle) {
|
||||
if (PreferencesUtils.isKey(context, R.string.stats_custom_layout_total_time_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_moving_time_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_pace_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_moving_pace_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_pace_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_fastest_pace_key, statTitle)) {
|
||||
return context.getString(R.string.stats_empty_value_time);
|
||||
} else if (PreferencesUtils.isKey(context, R.string.stats_custom_layout_distance_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_speed_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_speed_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_max_speed_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_moving_speed_key, statTitle)) {
|
||||
return context.getString(R.string.stats_empty_value_float);
|
||||
} else if (PreferencesUtils.isKey(context, R.string.stats_custom_layout_coordinates_key, statTitle)) {
|
||||
return context.getString(R.string.stats_empty_value_coordinates);
|
||||
} else {
|
||||
return context.getString(R.string.stats_empty_value_integer);
|
||||
}
|
||||
}
|
||||
|
||||
public static Layout filterVisible(Layout layout, boolean visible) {
|
||||
Layout result = new Layout(layout.getProfile());
|
||||
result.addFields(layout.getFields().stream().filter(f -> f.isVisible() == visible).collect(Collectors.toList()));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -124,10 +124,22 @@ public class StringUtils {
|
||||
/**
|
||||
* Formats a coordinate
|
||||
*
|
||||
* @param context the context's object.
|
||||
* @param coordinate the coordinate
|
||||
*/
|
||||
public static String formatCoordinate(double coordinate) {
|
||||
return Location.convert(coordinate, Location.FORMAT_DEGREES) + COORDINATE_DEGREE;
|
||||
public static String formatCoordinate(Context context, double coordinate) {
|
||||
return context.getString(R.string.location_coordinate, Location.convert(coordinate, Location.FORMAT_DEGREES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a complete coordinate (latitude, longitude)
|
||||
*
|
||||
* @param context the context's object.
|
||||
* @param latitude the coordinate's latitude.
|
||||
* @param longitude the coordinate's longitude.
|
||||
*/
|
||||
public static String formatCoordinate(Context context, double latitude, double longitude) {
|
||||
return context.getString(R.string.location_latitude_longitude, Location.convert(latitude, Location.FORMAT_DEGREES), Location.convert(longitude, Location.FORMAT_DEGREES));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,6 +203,33 @@ public class StringUtils {
|
||||
return new Pair<>(context.getString(R.string.time, minutes, seconds), unitString);
|
||||
}
|
||||
|
||||
public static Pair<String, String> getHeartRateParts(Context context, Float heartrate_bpm) {
|
||||
String value = context.getString(R.string.value_none);
|
||||
if (heartrate_bpm != null) {
|
||||
value = StringUtils.formatDecimal(heartrate_bpm, 0);
|
||||
}
|
||||
|
||||
return new Pair<>(value, context.getString(R.string.sensor_unit_beats_per_minute));
|
||||
}
|
||||
|
||||
public static Pair<String, String> getCadenceParts(Context context, Float cadence_rpm) {
|
||||
String value = context.getString(R.string.value_none);
|
||||
if (cadence_rpm != null) {
|
||||
value = StringUtils.formatDecimal(cadence_rpm, 0);
|
||||
}
|
||||
|
||||
return new Pair<>(value, context.getString(R.string.sensor_unit_rounds_per_minute));
|
||||
}
|
||||
|
||||
public static Pair<String, String> getPowerParts(Context context, Float power_w) {
|
||||
String value = context.getString(R.string.value_none);
|
||||
if (power_w != null) {
|
||||
value = StringUtils.formatDecimal(power_w, 0);
|
||||
}
|
||||
|
||||
return new Pair<>(value, context.getString(R.string.sensor_unit_power));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string for category.
|
||||
*
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorData;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCyclingPower;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
public class SensorDataModel {
|
||||
private int labelId;
|
||||
private final String sensorValue;
|
||||
private String sensorName;
|
||||
private int unitId;
|
||||
|
||||
public SensorDataModel(int labelId, int unitId, float sensorValue) {
|
||||
this.labelId = labelId;
|
||||
this.unitId = unitId;
|
||||
this.sensorValue = StringUtils.formatDecimal(sensorValue, 0);
|
||||
}
|
||||
|
||||
public SensorDataModel(SensorData<Float> sensorData) {
|
||||
this.sensorName = sensorData.getSensorNameOrAddress();
|
||||
this.sensorValue = sensorData.hasValue() && sensorData.isRecent() ? StringUtils.formatDecimal(sensorData.getValue(), 0) : null;
|
||||
if (sensorData instanceof SensorDataHeartRate) {
|
||||
this.labelId = R.string.sensor_state_heart_rate;
|
||||
this.unitId = R.string.sensor_unit_beats_per_minute;
|
||||
} else if (sensorData instanceof SensorDataCycling.Cadence) {
|
||||
this.labelId = R.string.sensor_state_cadence;
|
||||
this.unitId = R.string.sensor_unit_rounds_per_minute;
|
||||
} else if (sensorData instanceof SensorDataCyclingPower) {
|
||||
this.labelId = R.string.sensor_state_power;
|
||||
this.unitId = R.string.sensor_unit_power;
|
||||
}
|
||||
}
|
||||
|
||||
public int getLabelId() {
|
||||
return labelId;
|
||||
}
|
||||
|
||||
public int getUnitId() {
|
||||
return unitId;
|
||||
}
|
||||
|
||||
public String getSensorName() {
|
||||
return sensorName;
|
||||
}
|
||||
|
||||
public String getSensorValue() {
|
||||
return sensorValue;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return sensorValue != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
|
||||
public class StatisticData {
|
||||
private final DataField dataField;
|
||||
private final String value;
|
||||
private final String unit;
|
||||
private final String description;
|
||||
|
||||
public StatisticData(@NonNull DataField dataField, @NonNull Pair<String, String> valueAndUnit, String description) {
|
||||
this.dataField = dataField;
|
||||
this.value = valueAndUnit.first;
|
||||
this.unit = valueAndUnit.second;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public DataField getField() {
|
||||
return dataField;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
public String getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean hasDescription() {
|
||||
return description != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
public class StatisticDataBuilder {
|
||||
|
||||
public static List<StatisticData> fromRecordingData(@NonNull Context context, @NonNull TrackRecordingService.RecordingData recordingData, @NonNull Layout layout, boolean metricUnits) {
|
||||
List<StatisticData> statisticDataList = layout.getFields().stream()
|
||||
.filter(DataField::isVisible)
|
||||
.map(field -> build(context, recordingData, field.getKey(), field.isPrimary(), metricUnits))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
statisticDataList.addAll(getSensorStatsDataIfNeeded(context, recordingData, statisticDataList, metricUnits));
|
||||
return statisticDataList;
|
||||
}
|
||||
|
||||
private static StatisticData build(@NonNull Context context, @NonNull TrackRecordingService.RecordingData recordingData, @NonNull String fieldKey, boolean isPrimary, boolean metricUnits) {
|
||||
final TrackPoint latestTrackPoint = recordingData.getLatestTrackPoint();
|
||||
final SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
|
||||
|
||||
String title = null;
|
||||
String description = null;
|
||||
Pair<String, String> valueAndUnit = null;
|
||||
boolean isWide = false;
|
||||
if (fieldKey.equals(context.getString(R.string.stats_custom_layout_total_time_key))) {
|
||||
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(recordingData.getTrackStatistics().getTotalTime()), null);
|
||||
title = context.getString(R.string.stats_total_time);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_moving_time_key))) {
|
||||
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(recordingData.getTrackStatistics().getMovingTime()), null);
|
||||
title = context.getString(R.string.stats_moving_time);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_distance_key))) {
|
||||
valueAndUnit = StringUtils.getDistanceParts(context, recordingData.getTrackStatistics().getTotalDistance(), metricUnits);
|
||||
title = context.getString(R.string.stats_distance);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_speed_key)) || fieldKey.equals(context.getString(R.string.stats_custom_layout_pace_key))) {
|
||||
boolean reportSpeed = fieldKey.equals("speed");
|
||||
title = fieldKey.equals("speed") ? context.getString(R.string.stats_speed) : context.getString(R.string.stats_pace);
|
||||
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingDistanceSpeed() != null && sensorDataSet.getCyclingDistanceSpeed().hasValue() && sensorDataSet.getCyclingDistanceSpeed().isRecent()) {
|
||||
valueAndUnit = StringUtils.getSpeedParts(context, sensorDataSet.getCyclingDistanceSpeed().getValue().getSpeed(), metricUnits, reportSpeed);
|
||||
description = context.getString(R.string.description_speed_source_sensor, sensorDataSet.getCyclingDistanceSpeed().getSensorNameOrAddress());
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getSpeedParts(context, speed, metricUnits, reportSpeed);
|
||||
description = context.getString(R.string.description_speed_source_gps);
|
||||
}
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
|
||||
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getAverageMovingSpeed(), metricUnits, true);
|
||||
title = context.getString(R.string.stats_average_moving_speed);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_speed_key))) {
|
||||
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getAverageSpeed(), metricUnits, true);
|
||||
title = context.getString(R.string.stats_average_speed);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_max_speed_key))) {
|
||||
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getMaxSpeed(), metricUnits, true);
|
||||
title = context.getString(R.string.stats_max_speed);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
|
||||
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getAverageMovingSpeed(), metricUnits, false);
|
||||
title = context.getString(R.string.stats_average_moving_pace);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_pace_key))) {
|
||||
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getAverageSpeed(), metricUnits, false);
|
||||
title = context.getString(R.string.stats_average_pace);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_fastest_pace_key))) {
|
||||
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getMaxSpeed(), metricUnits, true);
|
||||
title = context.getString(R.string.stats_fastest_pace);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_altitude_key))) {
|
||||
Float altitude = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? (float) latestTrackPoint.getAltitude().toM() : null;
|
||||
title = context.getString(R.string.stats_altitude);
|
||||
description = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? context.getString(latestTrackPoint.getAltitude().getLabelId()) : null;
|
||||
valueAndUnit = StringUtils.getAltitudeParts(context, altitude, metricUnits);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_gain_key))) {
|
||||
valueAndUnit = StringUtils.getAltitudeParts(context, recordingData.getTrackStatistics().getTotalAltitudeGain(), metricUnits);
|
||||
title = context.getString(R.string.stats_gain);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_loss_key))) {
|
||||
valueAndUnit = StringUtils.getAltitudeParts(context, recordingData.getTrackStatistics().getTotalAltitudeLoss(), metricUnits);
|
||||
title = context.getString(R.string.stats_loss);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_coordinates_key))) {
|
||||
title = context.getString(R.string.stats_coordinates);
|
||||
isWide = true;
|
||||
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) {
|
||||
valueAndUnit = new Pair<>(StringUtils.formatCoordinate(context, latestTrackPoint.getLatitude(), latestTrackPoint.getLongitude()), null);
|
||||
} else {
|
||||
valueAndUnit = new Pair<>(context.getString(R.string.value_none), null);
|
||||
}
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_heart_rate_key))) {
|
||||
title = context.getString(R.string.stats_sensors_heart_rate);
|
||||
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null && sensorDataSet.getHeartRate().hasValue() && sensorDataSet.getHeartRate().isRecent()) {
|
||||
valueAndUnit = StringUtils.getHeartRateParts(context, sensorDataSet.getHeartRate().getValue());
|
||||
description = sensorDataSet.getHeartRate().getSensorNameOrAddress();
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getHeartRateParts(context, null);
|
||||
description = context.getString(R.string.value_none);
|
||||
}
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_cadence_key))) {
|
||||
title = context.getString(R.string.stats_sensors_cadence);
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingCadence() != null && sensorDataSet.getCyclingCadence().hasValue() && sensorDataSet.getCyclingCadence().isRecent()) {
|
||||
valueAndUnit = StringUtils.getCadenceParts(context, sensorDataSet.getCyclingCadence().getValue());
|
||||
description = sensorDataSet.getCyclingCadence().getSensorNameOrAddress();
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getCadenceParts(context, null);
|
||||
description = context.getString(R.string.value_none);
|
||||
}
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_power_key))) {
|
||||
title = context.getString(R.string.stats_sensors_power);
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue() && sensorDataSet.getCyclingPower().isRecent()) {
|
||||
valueAndUnit = StringUtils.getPowerParts(context, sensorDataSet.getCyclingPower().getValue());
|
||||
description = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getPowerParts(context, null);
|
||||
description = context.getString(R.string.value_none);
|
||||
}
|
||||
}
|
||||
|
||||
if (valueAndUnit == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new StatisticData(new DataField(fieldKey, title, true, isPrimary, isWide), valueAndUnit, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a list of StatisticData with sensors connected but not in statisticDataList.
|
||||
*/
|
||||
private static List<StatisticData> getSensorStatsDataIfNeeded(Context context, TrackRecordingService.RecordingData recordingData, List<StatisticData> statisticDataList, boolean metricUnits) {
|
||||
List<StatisticData> sensorDataList = new ArrayList<>();
|
||||
SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
|
||||
if (sensorDataSet == null) {
|
||||
return sensorDataList;
|
||||
}
|
||||
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))) && sensorDataSet.getHeartRate() != null && sensorDataSet.getHeartRate().hasValue() && sensorDataSet.getHeartRate().isRecent()) {
|
||||
sensorDataList.add(build(context, recordingData, "heart_rate", true, metricUnits));
|
||||
}
|
||||
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence))) && sensorDataSet.getCyclingCadence() != null && sensorDataSet.getCyclingCadence().hasValue() && sensorDataSet.getCyclingCadence().isRecent()) {
|
||||
sensorDataList.add(build(context, recordingData, "cadence", true, metricUnits));
|
||||
}
|
||||
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_power))) && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue() && sensorDataSet.getCyclingPower().isRecent()) {
|
||||
sensorDataList.add(build(context, recordingData, "power", true, metricUnits));
|
||||
}
|
||||
|
||||
return sensorDataList;
|
||||
}
|
||||
|
||||
public static List<StatisticData> fromSensorStatistics(@NonNull Context context, @NonNull SensorStatistics sensorStatistics) {
|
||||
List<StatisticData> sensorDataList = new ArrayList<>();
|
||||
|
||||
if (sensorStatistics.hasHeartRate()) {
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_heart_rate_key), context.getString(R.string.sensor_state_heart_rate_max), true, true, false),
|
||||
StringUtils.getHeartRateParts(context, sensorStatistics.getMaxHeartRate()),
|
||||
null
|
||||
)
|
||||
);
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_average_heart_rate_key), context.getString(R.string.sensor_state_heart_rate_avg), true, true, false),
|
||||
StringUtils.getHeartRateParts(context, sensorStatistics.getAvgHeartRate()),
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
if (sensorStatistics.hasCadence()) {
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_cadence_key), context.getString(R.string.sensor_state_cadence_max), true, true, false),
|
||||
StringUtils.getCadenceParts(context, sensorStatistics.getMaxCadence()),
|
||||
null
|
||||
)
|
||||
);
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_average_cadence_key), context.getString(R.string.sensor_state_cadence_avg), true, true, false),
|
||||
StringUtils.getCadenceParts(context, sensorStatistics.getAvgCadence()),
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
if (sensorStatistics.hasPower()) {
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_power_key), context.getString(R.string.sensor_state_power_avg), true, true, false),
|
||||
StringUtils.getPowerParts(context, sensorStatistics.getAvgPower()),
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return sensorDataList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
|
||||
public class StatisticsDataModel extends AndroidViewModel {
|
||||
|
||||
private MutableLiveData<List<StatisticData>> statsData;
|
||||
|
||||
public StatisticsDataModel(@NonNull Application application) {
|
||||
super(application);
|
||||
}
|
||||
|
||||
public MutableLiveData<List<StatisticData>> getStatsData() {
|
||||
if (statsData == null) {
|
||||
statsData = new MutableLiveData<>();
|
||||
}
|
||||
return statsData;
|
||||
}
|
||||
|
||||
public void update(TrackRecordingService.RecordingData recordingData, Layout layout, boolean metricUnit) {
|
||||
new Thread(() -> {
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(getApplication(), recordingData, layout, metricUnit);
|
||||
statsData.postValue(statisticDataList);
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M11,18c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2 0.9,-2 2,-2 2,0.9 2,2zM9,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM9,4c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM15,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM15,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM15,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/toolbar" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
style="@style/TextMediumPrimary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/settings_recording_customize_layout_select_columns" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_options"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view_visible"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/recycler_view_not_visible" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view_not_visible"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/recycler_view_visible"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/aggregated_stats_type_label"
|
||||
style="@style/StatsLargeLabel"
|
||||
style="@style/LargeLabel"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/aggregated_stats_num_tracks"
|
||||
style="@style/StatsLargeLabel"
|
||||
style="@style/LargeLabel"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
@@ -63,12 +63,9 @@
|
||||
tools:text="(1)" />
|
||||
|
||||
<!-- Distance -->
|
||||
|
||||
<!-- Time -->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/aggregated_stats_distance"
|
||||
style="@style/StatsLargeValue"
|
||||
style="@style/StatsPrimaryValue"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/aggregated_stats_distance_unit"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
@@ -86,9 +83,10 @@
|
||||
app:layout_constraintStart_toEndOf="@+id/aggregated_stats_distance"
|
||||
tools:text="km" />
|
||||
|
||||
<!-- Time -->
|
||||
<TextView
|
||||
android:id="@+id/aggregated_stats_time"
|
||||
style="@style/StatsLargeValue"
|
||||
style="@style/StatsPrimaryValue"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintEnd_toEndOf="@id/guideline3"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/StatsCardView">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/stats_iv_drag_indicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_baseline_drag_indicator_24"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_layout"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<include
|
||||
android:id="@+id/stats_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
layout="@layout/stats_item"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_iv_drag_indicator"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_icon_show_status"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/stats_icon_show_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:src="@drawable/ic_baseline_visibility_24"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_layout"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
@@ -88,7 +88,7 @@
|
||||
<!-- Distance -->
|
||||
<TextView
|
||||
android:id="@+id/stats_distance_label"
|
||||
style="@style/StatsLargeLabel"
|
||||
style="@style/LargeLabel"
|
||||
android:text="@string/stats_distance"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
@@ -97,7 +97,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_distance_value"
|
||||
style="@style/StatsLargeValue"
|
||||
style="@style/StatsPrimaryValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
@@ -116,7 +116,7 @@
|
||||
<!-- Moving time -->
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_time_label"
|
||||
style="@style/StatsLargeLabel"
|
||||
style="@style/LargeLabel"
|
||||
android:text="@string/stats_moving_time"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
@@ -125,7 +125,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_time_value"
|
||||
style="@style/StatsLargeValue"
|
||||
style="@style/StatsPrimaryValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
@@ -170,7 +170,7 @@
|
||||
<!-- Speed/Pace max -->
|
||||
<TextView
|
||||
android:id="@+id/stats_max_speed_label"
|
||||
style="@style/StatsLargeLabel"
|
||||
style="@style/LargeLabel"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||
@@ -179,7 +179,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_max_speed_value"
|
||||
style="@style/StatsLargeValue"
|
||||
style="@style/StatsPrimaryValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
@@ -198,7 +198,7 @@
|
||||
<!-- Avg. moving speed/pace -->
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_speed_label"
|
||||
style="@style/StatsLargeLabel"
|
||||
style="@style/LargeLabel"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
@@ -207,7 +207,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_speed_value"
|
||||
style="@style/StatsLargeValue"
|
||||
style="@style/StatsPrimaryValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
@@ -277,7 +277,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_altitude_gain_label"
|
||||
style="@style/StatsLargeLabel"
|
||||
style="@style/LargeLabel"
|
||||
android:text="@string/stats_gain"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
@@ -286,7 +286,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_altitude_gain_value"
|
||||
style="@style/StatsLargeValue"
|
||||
style="@style/StatsPrimaryValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
@@ -304,7 +304,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_altitude_loss_label"
|
||||
style="@style/StatsLargeLabel"
|
||||
style="@style/LargeLabel"
|
||||
android:text="@string/stats_loss"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
@@ -313,7 +313,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_altitude_loss_value"
|
||||
style="@style/StatsLargeValue"
|
||||
style="@style/StatsPrimaryValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
|
||||
@@ -1,457 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/StatsScrollView">
|
||||
style="@style/StatsScrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/stats_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintEnd_toStartOf="parent"
|
||||
app:layout_constraintStart_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="parent" />
|
||||
|
||||
<!-- Guidelines -->
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_begin="8dp" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_end="8dp" />
|
||||
|
||||
<!-- Distance -->
|
||||
<TextView
|
||||
android:id="@+id/stats_distance_label"
|
||||
style="@style/StatsLargeLabel"
|
||||
android:text="@string/stats_distance"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_distance_value"
|
||||
style="@style/StatsLargeValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_distance_unit"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_distance_label"
|
||||
tools:text="100" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_distance_unit"
|
||||
style="@style/StatsUnit"
|
||||
app:layout_constraintBottom_toBottomOf="@id/stats_distance_value"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_distance_value"
|
||||
tools:text="km" />
|
||||
|
||||
<!-- Activity Type -->
|
||||
<TextView
|
||||
android:id="@+id/stats_activity_type_label"
|
||||
style="@style/StatsLargeLabel"
|
||||
android:text="@string/track_edit_activity_type_hint"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/stats_activity_type_icon"
|
||||
style="@style/StatsLargeValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="@id/stats_distance_value"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintTop_toTopOf="@id/stats_distance_value"
|
||||
app:tint="?android:textColorPrimary" />
|
||||
|
||||
<!-- Total time -->
|
||||
<TextView
|
||||
android:id="@+id/stats_total_time_label"
|
||||
style="@style/StatsLargeLabel"
|
||||
android:text="@string/stats_total_time"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_distance_value" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_total_time_value"
|
||||
style="@style/StatsLargeValue"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_total_time_label"
|
||||
tools:text="00:00:00" />
|
||||
|
||||
<!-- Moving time -->
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_time_label"
|
||||
style="@style/StatsLargeLabel"
|
||||
android:text="@string/stats_moving_time"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="@id/stats_total_time_label"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintTop_toTopOf="@id/stats_total_time_label"
|
||||
app:layout_constraintVertical_bias="1.0" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_time_value"
|
||||
style="@style/StatsLargeValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_moving_time_label"
|
||||
tools:text="00:00:00" />
|
||||
|
||||
<!-- Barrier -->
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/stats_time_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="stats_total_time_value,stats_moving_time_value" />
|
||||
|
||||
<!-- Horizontal line -->
|
||||
<View
|
||||
android:id="@+id/stats_speed_horizontal_line"
|
||||
style="@style/StatsHorizontalLine"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_time_barrier" />
|
||||
|
||||
<!-- Speed/pace -->
|
||||
<TextView
|
||||
android:id="@+id/stats_speed_label"
|
||||
style="@style/StatsSmallLabel"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_speed_horizontal_line"
|
||||
tools:text="Speed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_speed_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_speed_unit"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_speed_label"
|
||||
tools:text="30.00" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_speed_unit"
|
||||
style="@style/StatsUnit"
|
||||
app:layout_constraintBottom_toBottomOf="@id/stats_speed_value"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_speed_value"
|
||||
tools:text="km/h" />
|
||||
|
||||
<!-- Average speed/pace -->
|
||||
<TextView
|
||||
android:id="@+id/stats_average_speed_label"
|
||||
style="@style/StatsSmallLabel"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_speed_horizontal_line"
|
||||
tools:text="Avg. Speed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_average_speed_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_average_speed_unit"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_label"
|
||||
tools:text="30.00" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_average_speed_unit"
|
||||
style="@style/StatsUnit"
|
||||
app:layout_constraintBottom_toBottomOf="@id/stats_average_speed_value"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_average_speed_value"
|
||||
tools:text="km/h" />
|
||||
|
||||
<!-- Speed max -->
|
||||
<TextView
|
||||
android:id="@+id/stats_max_speed_label"
|
||||
style="@style/StatsSmallLabel"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_value"
|
||||
tools:text="Max. Speed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_max_speed_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_max_speed_unit"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_max_speed_label"
|
||||
tools:text="30.00" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_max_speed_unit"
|
||||
style="@style/StatsUnit"
|
||||
app:layout_constraintBottom_toBottomOf="@id/stats_max_speed_value"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_max_speed_value"
|
||||
tools:text="km/h" />
|
||||
|
||||
<!-- Moving speed max -->
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_speed_label"
|
||||
style="@style/StatsSmallLabel"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_value"
|
||||
tools:text="Max. Moving Speed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_speed_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_moving_speed_unit"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_moving_speed_label"
|
||||
tools:text="30.00" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_moving_speed_unit"
|
||||
style="@style/StatsUnit"
|
||||
app:layout_constraintBottom_toBottomOf="@id/stats_moving_speed_value"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_moving_speed_value"
|
||||
tools:text="km/h" />
|
||||
|
||||
<!-- Barrier -->
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/stats_speed_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="stats_max_speed_value,stats_moving_speed_value" />
|
||||
|
||||
<!-- Horizontal line -->
|
||||
<View
|
||||
android:id="@+id/stats_altitude_horizontal_line"
|
||||
style="@style/StatsHorizontalLine"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_speed_barrier" />
|
||||
|
||||
<!-- altitude -->
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/stats_altitude_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="stats_altitude_horizontal_line,stats_altitude_current_label,stats_altitude_current_value,stats_altitude_current_unit,stats_altitude_gain_label,stats_altitude_gain_value,stats_altitude_gain_unit,stats_altitude_loss_label,stats_altitude_loss_value,stats_altitude_loss_unit,stats_altitude_current_label_egm" />
|
||||
|
||||
<!-- altitude -->
|
||||
<TextView
|
||||
android:id="@+id/stats_altitude_current_label"
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/stats_altitude"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_altitude_horizontal_line" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_altitude_current_label_egm"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="0dp"
|
||||
android:text="@string/egm2008"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_altitude_current_value" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_altitude_current_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_altitude_current_unit"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_altitude_current_label"
|
||||
tools:text="100" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_altitude_current_unit"
|
||||
style="@style/StatsUnit"
|
||||
app:layout_constraintBottom_toBottomOf="@id/stats_altitude_current_value"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_altitude_current_value"
|
||||
tools:text="m" />
|
||||
|
||||
<!-- Altitude gain -->
|
||||
<TextView
|
||||
android:id="@+id/stats_altitude_gain_label"
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/stats_gain"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_altitude_horizontal_line" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_altitude_gain_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_altitude_gain_unit"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_altitude_gain_label"
|
||||
tools:text="100" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_altitude_gain_unit"
|
||||
style="@style/StatsUnit"
|
||||
app:layout_constraintBottom_toBottomOf="@id/stats_altitude_gain_value"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_altitude_gain_value"
|
||||
tools:text="m" />
|
||||
|
||||
<!-- Altitude loss -->
|
||||
<TextView
|
||||
android:id="@+id/stats_altitude_loss_label"
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/stats_loss"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_altitude_gain_value" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_altitude_loss_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_altitude_loss_unit"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_altitude_loss_label"
|
||||
tools:text="100" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_altitude_loss_unit"
|
||||
style="@style/StatsUnit"
|
||||
app:layout_constraintBottom_toBottomOf="@id/stats_altitude_loss_value"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_altitude_loss_value"
|
||||
tools:text="m" />
|
||||
|
||||
<!-- Coordinate group -->
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/stats_coordinate_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="stats_latitude_label,stats_latitude_value,stats_longitude_label,stats_longitude_value,stats_coordinate_horizontal_line" />
|
||||
|
||||
<!-- Barrier -->
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/stats_altitude_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="stats_altitude_current_value,stats_altitude_gain_value,stats_altitude_loss_value" />
|
||||
|
||||
<!-- Horizontal line -->
|
||||
<View
|
||||
android:id="@+id/stats_coordinate_horizontal_line"
|
||||
style="@style/StatsHorizontalLine"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_altitude_barrier" />
|
||||
|
||||
<!-- Latitude -->
|
||||
<TextView
|
||||
android:id="@+id/stats_latitude_label"
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/stats_latitude"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_coordinate_horizontal_line" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_latitude_value"
|
||||
style="@style/StatsSmallValue"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_latitude_label"
|
||||
tools:text="0.834937848" />
|
||||
|
||||
<!-- Longitude -->
|
||||
<TextView
|
||||
android:id="@+id/stats_longitude_label"
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/stats_longitude"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_coordinate_horizontal_line" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_longitude_value"
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline2"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_longitude_label"
|
||||
tools:text="0.834937848" />
|
||||
|
||||
<!-- Barrier -->
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/stats_location_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="stats_latitude_value,stats_longitude_value" />
|
||||
|
||||
<!-- Horizontal line -->
|
||||
<View
|
||||
android:id="@+id/stats_sensor_horizontal_line"
|
||||
style="@style/StatsHorizontalLine"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_location_barrier" />
|
||||
|
||||
<!-- Sensors data -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/stats_sensors_recycler_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toStartOf="@id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_sensor_horizontal_line" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingTop="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_value"
|
||||
style="@style/StatsPrimaryValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_unit"
|
||||
tools:text="90" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_unit"
|
||||
style="@style/StatsUnit"
|
||||
tools:text="@string/sensor_unit_beats_per_minute"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_value"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/stats_value" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_description_main"
|
||||
style="@style/StatsPrimaryLabel"
|
||||
tools:text="@string/sensor_state_heart_rate"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_value" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_description_secondary"
|
||||
style="@style/StatsTinyValue"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_description_main"
|
||||
tools:visibility="visible"
|
||||
tools:text="90" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,42 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingTop="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_sensor_label"
|
||||
style="@style/StatsLargeLabel"
|
||||
android:id="@+id/stats_description_main"
|
||||
style="@style/LargeLabel"
|
||||
tools:text="@string/sensor_state_heart_rate"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_sensor_sensor_value"
|
||||
style="@style/StatsTinyValue"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_sensor_label"
|
||||
tools:text="90" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_sensor_value"
|
||||
style="@style/StatsLargeValue"
|
||||
android:id="@+id/stats_value"
|
||||
style="@style/StatsPrimaryValue"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_description_main"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_sensor_unit"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_sensor_sensor_value"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_unit"
|
||||
tools:text="90" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_sensor_unit"
|
||||
android:id="@+id/stats_unit"
|
||||
style="@style/StatsUnit"
|
||||
tools:text="@string/sensor_unit_beats_per_minute"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_sensor_value"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_value"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/stats_sensor_value" />
|
||||
app:layout_constraintBottom_toBottomOf="@id/stats_value" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_description_secondary"
|
||||
style="@style/StatsTinyValue"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_value"
|
||||
tools:visibility="visible"
|
||||
tools:text="90" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -217,7 +217,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">البلوتوث</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">خط العرض/خط الطول</string>
|
||||
<string name="settings_stats_rate_title">المعدل المفضل</string>
|
||||
<string name="settings_stats_units_imperial">وحدات القياس البريطانية (ميل، قدم)</string>
|
||||
<string name="settings_stats_units_metric">وحدات القياس المترية (كم، م)</string>
|
||||
|
||||
@@ -176,7 +176,6 @@
|
||||
<string name="settings_reset_confirm_title">Reset now?</string>
|
||||
<string name="settings_reset_done">All settings have been reverted to default values</string>
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<string name="settings_stats_coordinate">Latitude/Longitude</string>
|
||||
<string name="settings_stats_rate_title">Preferred rate</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (miles, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metric (km, m)</string>
|
||||
|
||||
@@ -178,7 +178,6 @@
|
||||
<string name="settings_reset_confirm_title">¿Restablecer ahora?</string>
|
||||
<string name="settings_reset_done">Se restablecieron los valores predeterminados de todas las opciones de configuración</string>
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<string name="settings_stats_coordinate">Latitud/longitud</string>
|
||||
<string name="settings_stats_rate_title">Tasa preferida</string>
|
||||
<string name="settings_stats_units_imperial">Imperiales (millas, pies)</string>
|
||||
<string name="settings_stats_units_metric">Métricas (km, m)</string>
|
||||
@@ -399,8 +398,8 @@
|
||||
<string name="help_feature_content">* crear pistas de GPS
|
||||
\n* utilizar un sensor de frecuencia cardíaca (solo Bluetooth LE)
|
||||
\n* anuncios de voz (por tiempo o distancia)
|
||||
\n* crear marcadores (incluidas imágenes)
|
||||
\n* funciona completamente sin conexión
|
||||
\n* crear marcadores (incluidas imágenes)
|
||||
\n* funciona completamente sin conexión
|
||||
\n* mostrar pistas en un mapa (ver más abajo)</string>
|
||||
<string name="help_track_map_title">¿Cómo mostrar un camino en un mapa\?</string>
|
||||
<string name="help_storage_pictures_title">¿Cómo almacena OpenTracks las imágenes que se tomaron como marcadores\?</string>
|
||||
|
||||
@@ -213,7 +213,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Ширина и дължина</string>
|
||||
<string name="settings_stats_rate_title">Предпочитана мярка</string>
|
||||
<string name="settings_stats_units_imperial">Имперски (мил, фт)</string>
|
||||
<string name="settings_stats_units_metric">Метрични (км, м)</string>
|
||||
|
||||
@@ -213,7 +213,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Latitud/Longitud</string>
|
||||
<string name="settings_stats_rate_title">Mesura preferida</string>
|
||||
<string name="settings_stats_units_imperial">Sistema anglosaxó (milles, peus)</string>
|
||||
<string name="settings_stats_units_metric">Sistema mètric (km, m)</string>
|
||||
|
||||
@@ -51,7 +51,7 @@ Pokud zařízení GPS hlásí nepřesná data (např. Polohu, rychlost, nadmořs
|
||||
\n b) zkuste zlepšit příjem GPS (např. vyjít ven),
|
||||
\n c) zkontrolujte, zda používáte aplikaci <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest </a> a
|
||||
\n d) deaktivujte úsporu energie.
|
||||
\n
|
||||
\n
|
||||
\n Pokud nic nepomůže, nahlašte jako chybu. </string>
|
||||
<string name="help_support_title">Jak mohu podpořit OpenTracks\?</string>
|
||||
<string name="help_support_content"> Můžete:
|
||||
@@ -280,7 +280,6 @@ Pokud zařízení GPS hlásí nepřesná data (např. Polohu, rychlost, nadmořs
|
||||
<string name="settings_sensor_connecting">Připojeno k senzoru %1$s</string>
|
||||
<string name="settings_sensor_disconnected">Odpojeno od %1$s</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Zeměpisná šířka/délka</string>
|
||||
<string name="settings_stats_rate_title">Preferovaná rychlost</string>
|
||||
<string name="settings_stats_units_imperial">Imperiální jednotky (míle, stopy)</string>
|
||||
<string name="settings_stats_units_metric">Metrické jednotky (km, m)</string>
|
||||
|
||||
@@ -282,7 +282,6 @@ Hvis GPS-enheden rapporterer unøjagtige data (f.eks. Placering, hastighed, høj
|
||||
<string name="settings_sensor_connecting">Tilsluttet %1$s</string>
|
||||
<string name="settings_sensor_disconnected">Frakoblet %1$s</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Breddegrad/længdegrad</string>
|
||||
<string name="settings_stats_rate_title">Foretrukkenfartenhed</string>
|
||||
<string name="settings_stats_units_imperial">Britisk (mil, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metrisk (km, m)</string>
|
||||
|
||||
@@ -284,7 +284,6 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
|
||||
<string name="settings_sensor_connecting">Verbunden mit %1$s</string>
|
||||
<string name="settings_sensor_disconnected">Verbindung getrennt von %1$s</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Breiten-/Längengrad</string>
|
||||
<string name="settings_stats_rate_title">Bevorzugtes Maß</string>
|
||||
<string name="settings_stats_units_imperial">Englisch (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metrisch (km, m)</string>
|
||||
|
||||
@@ -213,7 +213,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Πλάτος/Μήκος</string>
|
||||
<string name="settings_stats_rate_title">Προτιμώμενος ρυθμός</string>
|
||||
<string name="settings_stats_units_imperial">Αγγλοσαξονικό (μίλια, πόδια)</string>
|
||||
<string name="settings_stats_units_metric">Μετρικό (χλμ, μέτρα)</string>
|
||||
|
||||
@@ -279,7 +279,6 @@ Si el dispositivo GPS reporta datos inexactos (por ejemplo, ubicación, velocida
|
||||
<string name="settings_sensor_connecting">Conectado a %1$s</string>
|
||||
<string name="settings_sensor_disconnected">Desconectado de %1$s</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Latitud/Longitud</string>
|
||||
<string name="settings_stats_rate_title">Ritmo preferido</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (millas, pies)</string>
|
||||
<string name="settings_stats_units_metric">Métrica (km, m)</string>
|
||||
|
||||
@@ -279,7 +279,6 @@ Kui GPS-seade teatab ebatäpsetest andmetest (nt asukoht, kiirus, kõrgus), ei s
|
||||
<string name="settings_sensor_connecting">Ühendatud anduriga %1$s</string>
|
||||
<string name="settings_sensor_disconnected">Lahti ühendatud %1$s</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Laiuskraad/pikkuskraad</string>
|
||||
<string name="settings_stats_rate_title">Eelistatud määr</string>
|
||||
<string name="settings_stats_units_imperial">Inglise mõõtühikud (miili, jalga)</string>
|
||||
<string name="settings_stats_units_metric">Meetermõõdustik (km, m)</string>
|
||||
|
||||
@@ -280,7 +280,6 @@ GPS gailuak datu okerrak salatzen baditu (adibidez, kokapena, abiadura, kota), O
|
||||
<string name="settings_sensor_connecting">Honekin konektatuta: %1$s</string>
|
||||
<string name="settings_sensor_disconnected">Deskonektatuta %1$s</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Latitudea/Longitudea</string>
|
||||
<string name="settings_stats_rate_title">Erritmo hobetsia</string>
|
||||
<string name="settings_stats_units_imperial">Inperiala (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metrikoa (km, m)</string>
|
||||
|
||||
@@ -213,7 +213,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">بلوتوث</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">عرض جغرافیایی/ طول جغرافیایی</string>
|
||||
<string name="settings_stats_rate_title">اولویت سنجه سرعت</string>
|
||||
<string name="settings_stats_units_imperial">انگلیسی (مایل، فوت)</string>
|
||||
<string name="settings_stats_units_metric">متریک (کیلومتر، متر)</string>
|
||||
|
||||
@@ -284,7 +284,6 @@ limitations under the License.
|
||||
<string name="settings_sensor_connecting">Yhdistetty: %1$s</string>
|
||||
<string name="settings_sensor_disconnected">Yhteys katkaistu: %1$s</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Leveys-/pituusaste</string>
|
||||
<string name="settings_stats_rate_title">Haluttu nopeus</string>
|
||||
<string name="settings_stats_units_imperial">Brittiläinen järj.
|
||||
(maili, jalka)</string>
|
||||
|
||||
@@ -176,7 +176,6 @@
|
||||
<string name="settings_reset_confirm_title">Réinitialiser maintenant?</string>
|
||||
<string name="settings_reset_done">Les valeurs par défaut de tous les paramètres ont bien été rétablies</string>
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<string name="settings_stats_coordinate">Latitude/longitude</string>
|
||||
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
|
||||
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
|
||||
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
|
||||
|
||||
@@ -176,7 +176,6 @@
|
||||
<string name="settings_reset_confirm_title">Réinitialiser maintenant ?</string>
|
||||
<string name="settings_reset_done">Les valeurs par défaut de tous les paramètres ont bien été rétablies.</string>
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<string name="settings_stats_coordinate">Latitude/Longitude</string>
|
||||
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
|
||||
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
|
||||
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
|
||||
|
||||
@@ -279,7 +279,6 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<string name="settings_sensor_connecting">Connecté à %1$s</string>
|
||||
<string name="settings_sensor_disconnected">Déconnecté de %1$s</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Latitude/Longitude</string>
|
||||
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
|
||||
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
|
||||
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
|
||||
@@ -498,8 +497,8 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<string name="import_progress_summary_exists_msg">Il existe déjà</string>
|
||||
<string name="import_progress_summary_errors_msg">Erreur</string>
|
||||
<plurals name="generic_completed_with_errors">
|
||||
<item quantity="one" tools:ignore="ImpliedQuantity">Terminé avec une erreur</item>
|
||||
<item quantity="other">Terminé avec des erreurs %1$d</item>
|
||||
<item quantity="one">Terminé avec %1$d erreur</item>
|
||||
<item quantity="other">Terminé avec des %1$d erreurs</item>
|
||||
</plurals>
|
||||
<string name="settings_default_trackfileformat">Format de fichier d\'exportation / partage</string>
|
||||
<string name="description_altitude_loss">Dénivelé négatif : %1$d m (%2$d ft)</string>
|
||||
|
||||
@@ -294,7 +294,6 @@ Se te detés ou estás en interiores, non se gardarán datos do sensor (mais ser
|
||||
<string name="settings_sensor_disconnected">Desconectado de %1$s</string>
|
||||
<string name="settings_sensor_wheel_circumference">Circunferencia da roda (mm)</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Latitude/lonxitude</string>
|
||||
<string name="settings_stats_rate_title">Indicador preferido</string>
|
||||
<string name="settings_stats_units_imperial">Imperiais (millas, pés)</string>
|
||||
<string name="settings_stats_units_metric">Métricas (km, m)</string>
|
||||
|
||||
@@ -213,7 +213,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">अक्षांश/देशांतर</string>
|
||||
<string name="settings_stats_rate_title">पसंदीदा दर</string>
|
||||
<string name="settings_stats_units_imperial">इंपीरियल (मील, फुट)</string>
|
||||
<string name="settings_stats_units_metric">मेट्रिक (किमी, मी)</string>
|
||||
|
||||
@@ -214,7 +214,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Geografska širina/duljina</string>
|
||||
<string name="settings_stats_rate_title">Preferirana stopa</string>
|
||||
<string name="settings_stats_units_imperial">Imperijalne (milje, stope)</string>
|
||||
<string name="settings_stats_units_metric">Metričke (km, m)</string>
|
||||
|
||||
@@ -213,7 +213,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Szélesség/hosszúság</string>
|
||||
<string name="settings_stats_rate_title">Mérték választása</string>
|
||||
<string name="settings_stats_units_imperial">Angolszász (mérföld, láb)</string>
|
||||
<string name="settings_stats_units_metric">Metrikus (km, m)</string>
|
||||
|
||||
@@ -175,7 +175,6 @@
|
||||
<string name="settings_reset_confirm_title">Setel ulang sekarang?</string>
|
||||
<string name="settings_reset_done">Semua setelan telah dikembalikan ke nilai default</string>
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<string name="settings_stats_coordinate">Lintang/Bujur</string>
|
||||
<string name="settings_stats_rate_title">Laju yang dipilih</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (mil, kaki)</string>
|
||||
<string name="settings_stats_units_metric">Metrik (km, m)</string>
|
||||
|
||||
@@ -255,7 +255,6 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Latitudine/longitudine</string>
|
||||
<string name="settings_stats_rate_title">Velocità preferita</string>
|
||||
<string name="settings_stats_units_imperial">Imperiale (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metrico (km, m)</string>
|
||||
|
||||
@@ -178,7 +178,6 @@
|
||||
<string name="settings_reset_confirm_title">לאפס כעת?</string>
|
||||
<string name="settings_reset_done">כל ההגדרות הוחזרו לערכי ברירת המחדל</string>
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<string name="settings_stats_coordinate">קו רוחב/קו אורך</string>
|
||||
<string name="settings_stats_rate_title">קצב מועדף</string>
|
||||
<string name="settings_stats_units_imperial">אימפריאלי (מייל, רגל)</string>
|
||||
<string name="settings_stats_units_metric">מטרי (ק\"מ, מ\')</string>
|
||||
|
||||
@@ -212,7 +212,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">緯度/経度</string>
|
||||
<string name="settings_stats_rate_title">計測方法</string>
|
||||
<string name="settings_stats_units_imperial">ヤード・ポンド法(マイル、フィート)</string>
|
||||
<string name="settings_stats_units_metric">メートル法(km、m)</string>
|
||||
|
||||
@@ -212,7 +212,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">블루투스</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">위도/경도</string>
|
||||
<string name="settings_stats_rate_title">선호하는 속도</string>
|
||||
<string name="settings_stats_units_imperial">야드파운드법(마일, 피트)</string>
|
||||
<string name="settings_stats_units_metric">미터법(km,m)</string>
|
||||
|
||||
@@ -214,7 +214,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Platuma ir ilguma</string>
|
||||
<string name="settings_stats_rate_title">Pageidaujamas greitis</string>
|
||||
<string name="settings_stats_units_imperial">Imperinės (myl., pėdos)</string>
|
||||
<string name="settings_stats_units_metric">Metrinės (km, m)</string>
|
||||
|
||||
@@ -214,7 +214,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Platums/garums</string>
|
||||
<string name="settings_stats_rate_title">Vēlamais rādītājs</string>
|
||||
<string name="settings_stats_units_imperial">Standarta (jūdzes, pēdas)</string>
|
||||
<string name="settings_stats_units_metric">Metriskās (km, m)</string>
|
||||
|
||||
@@ -177,7 +177,6 @@
|
||||
<string name="settings_reset_confirm_title">Resetați acum?</string>
|
||||
<string name="settings_reset_done">Toate setările au revenit la valorile prestabilite</string>
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<string name="settings_stats_coordinate">Latitudine/longitudine</string>
|
||||
<string name="settings_stats_rate_title">Rata de preferat</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metric (km, m)</string>
|
||||
|
||||
@@ -212,7 +212,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Latitud/Longitud</string>
|
||||
<string name="settings_stats_rate_title">Kadar pilihan</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (bt, kaki)</string>
|
||||
<string name="settings_stats_units_metric">Metrik (km, m)</string>
|
||||
|
||||
@@ -213,7 +213,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Bredde- og lengdegrad</string>
|
||||
<string name="settings_stats_rate_title">Foretrukket måling</string>
|
||||
<string name="settings_stats_units_imperial">Britisk (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metrisk (km, m)</string>
|
||||
|
||||
@@ -279,7 +279,6 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="settings_sensor_connecting">Verbonden met %1$s</string>
|
||||
<string name="settings_sensor_disconnected">Ontkoppeld van %1$s</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Breedte-/lengtegraad</string>
|
||||
<string name="settings_stats_rate_title">Voorkeursverhouding</string>
|
||||
<string name="settings_stats_units_imperial">Engels (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metrisch (km, m)</string>
|
||||
|
||||
@@ -242,7 +242,6 @@ limitations under the License.
|
||||
<string name="settings_sensor_disconnected">Rozłaczony z %1$s</string>
|
||||
<string name="settings_sensor_wheel_circumference">Obwód koła (mm)</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Długości i szerokości geograficznej</string>
|
||||
<string name="settings_stats_rate_title">Wybór tempo/prędkość</string>
|
||||
<string name="settings_stats_units_imperial">Imperialne (mile, stopy)</string>
|
||||
<string name="settings_stats_units_metric">Metryczne (km, m)</string>
|
||||
|
||||
@@ -176,7 +176,6 @@
|
||||
<string name="settings_reset_confirm_title">Redefinir agora?</string>
|
||||
<string name="settings_reset_done">Todas as configurações foram revertidas para os valores padrão</string>
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<string name="settings_stats_coordinate">Latitude/longitude</string>
|
||||
<string name="settings_stats_rate_title">Taxa preferencial</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (milhas, pés)</string>
|
||||
<string name="settings_stats_units_metric">Métrica (km, m)</string>
|
||||
|
||||
@@ -176,7 +176,6 @@
|
||||
<string name="settings_reset_confirm_title">Repor agora?</string>
|
||||
<string name="settings_reset_done">Todas as definições foram revertidas para os valores predefinidos</string>
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<string name="settings_stats_coordinate">Latitude/Longitude</string>
|
||||
<string name="settings_stats_rate_title">Avaliação preferida</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (milhas, pés)</string>
|
||||
<string name="settings_stats_units_metric">Métrico (km, m)</string>
|
||||
|
||||
@@ -213,7 +213,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Latitude/longitude</string>
|
||||
<string name="settings_stats_rate_title">Taxa preferencial</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (milhas, pés)</string>
|
||||
<string name="settings_stats_units_metric">Métrica (km, m)</string>
|
||||
|
||||
@@ -214,7 +214,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Latitudine/longitudine</string>
|
||||
<string name="settings_stats_rate_title">Rata de preferat</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metrică (km, m)</string>
|
||||
|
||||
@@ -215,7 +215,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Широта/долгота</string>
|
||||
<string name="settings_stats_rate_title">Предпочтение</string>
|
||||
<string name="settings_stats_units_imperial">Британская (мили, футы)</string>
|
||||
<string name="settings_stats_units_metric">Метрическая (км, м)</string>
|
||||
@@ -517,7 +516,7 @@ limitations under the License.
|
||||
<string name="menu_voice_rate">Скорость голоса</string>
|
||||
<string name="track_delete_not_recording">Эта дорожка не может быть удалена, поскольку она записана в данный момент.</string>
|
||||
<plurals name="generic_completed_with_errors">
|
||||
<item quantity="one">Завершена с %1$d ошибкой</item>
|
||||
<item quantity="one">Завершена с %1$d ошибкой</item>
|
||||
<item quantity="few">Завершено с %1$d ошибками</item>
|
||||
<item quantity="many">Завершено с %1$d ошибок</item>
|
||||
<item quantity="other">Завершено с %1$d ошибок</item>
|
||||
|
||||
@@ -214,7 +214,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Zemepis. šírka/dĺžka</string>
|
||||
<string name="settings_stats_rate_title">Preferovaná hodnota</string>
|
||||
<string name="settings_stats_units_imperial">Imperiálne jednotky (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metrické jednotky (km, m)</string>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -12,8 +13,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
--><resources>
|
||||
<!-- Activity Type -->
|
||||
<string name="activity_type_airplane">letalo</string>
|
||||
<string name="activity_type_atv">štirikolesniki</string>
|
||||
@@ -215,12 +215,10 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Zemlj. širina/dolžina</string>
|
||||
<string name="settings_stats_rate_title">Pred. enot. za hitr.</string>
|
||||
<string name="settings_stats_units_imperial">Imperialni sistem (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metrični sistem (km, m)</string>
|
||||
<string name="settings_stats_units_title">Prednostne enote</string>
|
||||
|
||||
<!-- Share Track -->
|
||||
<string name="share_track_share_file_body">Zdi se mi, da bi te ta pot utegnila zanimati.
|
||||
\n
|
||||
@@ -384,4 +382,115 @@ limitations under the License.
|
||||
<string name="waypoint_type_waterfall">slap</string>
|
||||
<string name="waypoint_type_water_fountain">fontana</string>
|
||||
<string name="waypoint_type_waypoint">smerna točka</string>
|
||||
</resources>
|
||||
<string name="help_track_map_title"></string>
|
||||
<string name="export_without_photos"></string>
|
||||
<string name="settings_default_trackfileformat"></string>
|
||||
<string name="sensor_state_heart_rate_avg"></string>
|
||||
<string name="track_delete_not_recording"></string>
|
||||
<string name="track_detail_intervals_tab"></string>
|
||||
<string name="generic_separator_done_total"></string>
|
||||
<string name="settings_night_mode_option_day"></string>
|
||||
<string name="settings_sensor_cycling_distance_speed"></string>
|
||||
<string name="settings_sensor_wheel_circumference"></string>
|
||||
<string name="stats_loss"></string>
|
||||
<string name="gps_disabled_msg"></string>
|
||||
<string name="track_distance_notification"></string>
|
||||
<string name="about_history"></string>
|
||||
<string name="settings_sensor_cycling_cadence"></string>
|
||||
<string name="voice_speed_lap"></string>
|
||||
<string name="export_error_post_workout"></string>
|
||||
<string name="menu_insert_gallery_img"></string>
|
||||
<string name="export_progress_summary_overwrite_msg"></string>
|
||||
<string name="export_with_photos"></string>
|
||||
<string name="sensor_state_cadence_max"></string>
|
||||
<string name="settings_sensor_connecting"></string>
|
||||
<string name="track_speed_notification"></string>
|
||||
<string name="description_default_speed_or_pace"></string>
|
||||
<string name="import_parser_error"></string>
|
||||
<string name="generic_show_errors"></string>
|
||||
<string name="stats_split_interval"></string>
|
||||
<string name="generic_completed"></string>
|
||||
<string name="bluetooth_disabled"></string>
|
||||
<string name="track_recording_notification_accuracy"></string>
|
||||
<string name="export_progress_summary_new_msg"></string>
|
||||
<string name="export_progress_summary_skip_msg"></string>
|
||||
<string name="hold_to_pause"></string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_summary"></string>
|
||||
<string name="help_gps_troubleshooting_title"></string>
|
||||
<string name="hold_to_stop"></string>
|
||||
<string name="menu_voice_rate"></string>
|
||||
<string name="import_progress_summary_errors_msg"></string>
|
||||
<string name="settings_night_mode_option_night"></string>
|
||||
<string name="help_data_google_play_services_title"></string>
|
||||
<string name="help_recording_content"></string>
|
||||
<string name="interval_list_empty_message"></string>
|
||||
<string name="share_image_subject"></string>
|
||||
<string name="help_data_storage_title"></string>
|
||||
<string name="help_data_storage_content"></string>
|
||||
<string name="instant_export_enabled_title"></string>
|
||||
<string name="import_progress_summary_exists_msg"></string>
|
||||
<string name="import_file_imported"></string>
|
||||
<string name="settings_night_mode_title"></string>
|
||||
<string name="gps_fixed_and_ready"></string>
|
||||
<string name="permission_gps_failed"></string>
|
||||
<string name="about_libraries"></string>
|
||||
<string name="open_track_as_KML"></string>
|
||||
<string name="voice_pace_per_mile"></string>
|
||||
<string name="about_preference_title"></string>
|
||||
<string name="import_unsupported_format"></string>
|
||||
<string name="settings_sensor_disconnected"></string>
|
||||
<string name="track_pace_notification"></string>
|
||||
<string name="help_feature_title"></string>
|
||||
<string name="sensor_state_power_avg"></string>
|
||||
<string name="about_description"></string>
|
||||
<string name="help_storage_pictures_title"></string>
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_summary"></string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_summary"></string>
|
||||
<string name="export_track_already_exists_msg"></string>
|
||||
<string name="about_version_code"></string>
|
||||
<string name="menu_resume_track"></string>
|
||||
<string name="open_track_as_GPX"></string>
|
||||
<string name="instant_export_enabled_summary"></string>
|
||||
<string name="import_progress_summary_ok_msg"></string>
|
||||
<string name="settings_night_mode_option_system"></string>
|
||||
<string name="activity_type_escooter"></string>
|
||||
<string name="help_recording_title"></string>
|
||||
<string name="settings_default_export_uri_title"></string>
|
||||
<string name="generic_skip"></string>
|
||||
<string name="help_track_export_content"></string>
|
||||
<string name="open_track_as_trackfileformat"></string>
|
||||
<string name="track_delete_progress"></string>
|
||||
<string name="generic_click_twice_cancel"></string>
|
||||
<string name="share_image_body"></string>
|
||||
<string name="help_storage_pictures_content"></string>
|
||||
<string name="import_prevent_reimport"></string>
|
||||
<string name="delete_service"></string>
|
||||
<string name="aggregated_stats_empty_message"></string>
|
||||
<string name="import_thread_interrupted"></string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_title"></string>
|
||||
<string name="import_no_kml_file_found"></string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_title"></string>
|
||||
<string name="import_error_info"></string>
|
||||
<string name="settings_prevent_reimport_tracks_title"></string>
|
||||
<string name="sensor_state_cadence_avg"></string>
|
||||
<string name="marker_add_photo_canceled"></string>
|
||||
<string name="voice_pace_lap"></string>
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_title"></string>
|
||||
<string name="export_progress_summary_errors_msg"></string>
|
||||
<string name="sensor_could_not_scan"></string>
|
||||
<string name="about_url"></string>
|
||||
<string name="description_altitude_loss"></string>
|
||||
<string name="generic_overwrite"></string>
|
||||
<string name="about_version_name"></string>
|
||||
<string name="help_feature_content"></string>
|
||||
<string name="export_all_do_it_for_all"></string>
|
||||
<string name="settings_error_initial_values_restored"></string>
|
||||
<string name="sensor_state_heart_rate_max"></string>
|
||||
<string name="help_track_export_title"></string>
|
||||
<string name="import_error_list_dialog_title"></string>
|
||||
<string name="export_track_errors"></string>
|
||||
<string name="about_third_party_data"></string>
|
||||
<string name="import_unable_to_import_file"></string>
|
||||
<string name="generic_open_track"></string>
|
||||
<string name="help_support_title"></string>
|
||||
</resources>
|
||||
@@ -214,7 +214,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Географска шир./дуж.</string>
|
||||
<string name="settings_stats_rate_title">Жељени темпо</string>
|
||||
<string name="settings_stats_units_imperial">Империјалне (ми, ст)</string>
|
||||
<string name="settings_stats_units_metric">Метричке (км, м)</string>
|
||||
|
||||
@@ -213,7 +213,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Latitud/longitud</string>
|
||||
<string name="settings_stats_rate_title">Föredragen mätning</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metrisk (km, m)</string>
|
||||
|
||||
@@ -212,7 +212,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">บลูทูธ</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">ละติจูด/ลองจิจูด</string>
|
||||
<string name="settings_stats_rate_title">อัตราที่ต้องการ</string>
|
||||
<string name="settings_stats_units_imperial">หน่วยอังกฤษ (ไมล์, ฟุต)</string>
|
||||
<string name="settings_stats_units_metric">หน่วยเมตริก (กม., เมตร)</string>
|
||||
|
||||
@@ -213,7 +213,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Latitude/Longtitude</string>
|
||||
<string name="settings_stats_rate_title">Gustong rate</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metric (km, m)</string>
|
||||
|
||||
@@ -213,7 +213,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Enlem/Boylam</string>
|
||||
<string name="settings_stats_rate_title">Tercih edilen oran</string>
|
||||
<string name="settings_stats_units_imperial">İngiliz (mil, fit)</string>
|
||||
<string name="settings_stats_units_metric">Metrik (km, m)</string>
|
||||
|
||||
@@ -215,7 +215,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Широта/довгота</string>
|
||||
<string name="settings_stats_rate_title">Система вимірювання</string>
|
||||
<string name="settings_stats_units_imperial">Британська (милі, фути)</string>
|
||||
<string name="settings_stats_units_metric">Метрична (км, м)</string>
|
||||
|
||||
@@ -212,7 +212,6 @@ limitations under the License.
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Vĩ độ/Kinh độ</string>
|
||||
<string name="settings_stats_rate_title">Tốc độ ưa thích</string>
|
||||
<string name="settings_stats_units_imperial">Hệ đo lường Anh (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Hệ mét (km, m)</string>
|
||||
|
||||
@@ -175,7 +175,6 @@
|
||||
<string name="settings_reset_confirm_title">確定要立即重設?</string>
|
||||
<string name="settings_reset_done">所有設定已還原為預設值</string>
|
||||
<string name="settings_sensor_bluetooth">藍牙</string>
|
||||
<string name="settings_stats_coordinate">緯度/經度</string>
|
||||
<string name="settings_stats_rate_title">偏好速率</string>
|
||||
<string name="settings_stats_units_imperial">英制 (英里、英尺)</string>
|
||||
<string name="settings_stats_units_metric">公制 (公里、公尺)</string>
|
||||
|
||||
@@ -175,7 +175,6 @@
|
||||
<string name="settings_reset_confirm_title">確定要立即重設?</string>
|
||||
<string name="settings_reset_done">所有設定已還原為預設值</string>
|
||||
<string name="settings_sensor_bluetooth">藍牙</string>
|
||||
<string name="settings_stats_coordinate">緯度/經度</string>
|
||||
<string name="settings_stats_rate_title">偏好速率</string>
|
||||
<string name="settings_stats_units_imperial">英制 (英里、英尺)</string>
|
||||
<string name="settings_stats_units_metric">公制 (公里、公尺)</string>
|
||||
|
||||
@@ -227,7 +227,6 @@ limitations under the License.
|
||||
<string name="settings_sensor_bluetooth">蓝牙</string>
|
||||
<string name="settings_sensor_wheel_circumference">车轮周长 (mm)</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">纬度/经度</string>
|
||||
<string name="settings_stats_rate_title">首选速率</string>
|
||||
<string name="settings_stats_units_imperial">英制(英里、英尺)</string>
|
||||
<string name="settings_stats_units_metric">公制(公里、米)</string>
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Do not translate anything in this file directly. -->
|
||||
<string name="stats_show_coordinate_key" translatable="false">statsShowCoordinate</string>
|
||||
<bool name="stats_show_coordinate_default" translatable="false">true</bool>
|
||||
|
||||
<string name="stats_show_grade_altitude_key" translatable="false">statsShowGradeElevation</string>
|
||||
<bool name="stats_show_altitude_default" translatable="false">true</bool>
|
||||
|
||||
<string name="stats_show_on_lockscreen_while_recording_key" translatable="false">trackdetail_show_on_lockscreen_while_recording</string>
|
||||
<bool name="stats_show_on_lockscreen_while_recording_default" translatable="false">false</bool>
|
||||
|
||||
@@ -17,6 +11,7 @@
|
||||
<bool name="stats_fullscreen_while_recording_default" translatable="false">false</bool>
|
||||
|
||||
<string name="settings_reset_key" translatable="false">settingsReset</string>
|
||||
<string name="settings_layout_reset_key" translatable="false">settingsLayoutReset</string>
|
||||
|
||||
<string name="settings_sensor_bluetooth_heart_rate_key" translatable="false">bluetoothSensor</string>
|
||||
<string name="settings_sensor_bluetooth_cycling_cadence_key" translatable="false">bluetoothCyclingCadenceSensor</string>
|
||||
@@ -278,4 +273,130 @@
|
||||
<item>@string/settings_night_mode_option_day</item>
|
||||
<item>@string/settings_night_mode_option_night</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
||||
<!-- Stats layout customization -->
|
||||
<string name="stats_custom_layout_key" translatable="false">statsCustomLayoutKey</string>
|
||||
|
||||
<!-- Stats layout customization: number of columns per row -->
|
||||
<string name="stats_custom_layout_columns_key" translatable="false">statsCustomLayoutColumnsKey</string>
|
||||
<integer name="stats_custom_layout_columns_default" translatable="false">2</integer>
|
||||
|
||||
<!-- Stats layout customization: key -->
|
||||
<string name="stats_custom_layout_fields_key" translatable="false">statsCustomLayoutFieldsKey</string>
|
||||
<!-- Stats keys -->
|
||||
<string name="stats_custom_layout_distance_key" translatable="false">distance</string>
|
||||
<string name="stats_custom_layout_total_time_key" translatable="false">total_time</string>
|
||||
<string name="stats_custom_layout_speed_key" translatable="false">speed</string>
|
||||
<string name="stats_custom_layout_moving_time_key" translatable="false">moving_time</string>
|
||||
<string name="stats_custom_layout_average_speed_key" translatable="false">average_speed</string>
|
||||
<string name="stats_custom_layout_max_speed_key" translatable="false">max_speed</string>
|
||||
<string name="stats_custom_layout_average_moving_speed_key" translatable="false">average_moving_speed</string>
|
||||
<string name="stats_custom_layout_altitude_key" translatable="false">altitude</string>
|
||||
<string name="stats_custom_layout_gain_key" translatable="false">gain</string>
|
||||
<string name="stats_custom_layout_loss_key" translatable="false">loss</string>
|
||||
<string name="stats_custom_layout_pace_key" translatable="false">pace</string>
|
||||
<string name="stats_custom_layout_average_moving_pace_key" translatable="false">average_moving_pace</string>
|
||||
<string name="stats_custom_layout_average_pace_key" translatable="false">average_pace</string>
|
||||
<string name="stats_custom_layout_fastest_pace_key" translatable="false">fastest_pace</string>
|
||||
<string name="stats_custom_layout_coordinates_key" translatable="false">coordinates</string>
|
||||
<string name="stats_custom_layout_heart_rate_key" translatable="false">heart_rate</string>
|
||||
<string name="stats_custom_layout_average_heart_rate_key" translatable="false">average_heart_rate</string>
|
||||
<string name="stats_custom_layout_cadence_key" translatable="false">cadence</string>
|
||||
<string name="stats_custom_layout_average_cadence_key" translatable="false">average_cadence</string>
|
||||
<string name="stats_custom_layout_power_key" translatable="false">power</string>
|
||||
<!--
|
||||
Default dataFields for stats layout customization in a set of arrays.
|
||||
Every array has two items:
|
||||
- Key value for the dataField.
|
||||
- Comma separated flags for: visible, primary and span/wide for the dataField.
|
||||
-->
|
||||
<string name="stats_custom_layout_fields_default_value_separator" translatable="false">_</string>
|
||||
<string-array name="stats_custom_layout_fields_default_value_0">
|
||||
<item>@string/stats_custom_layout_distance_key</item>
|
||||
<item>1,1,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_1">
|
||||
<item>@string/stats_custom_layout_total_time_key</item>
|
||||
<item>1,1,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_2">
|
||||
<item>@string/stats_custom_layout_speed_key</item>
|
||||
<item>1,1,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_3">
|
||||
<item>@string/stats_custom_layout_moving_time_key</item>
|
||||
<item>1,1,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_4">
|
||||
<item>@string/stats_custom_layout_average_speed_key</item>
|
||||
<item>1,0,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_5">
|
||||
<item>@string/stats_custom_layout_max_speed_key</item>
|
||||
<item>1,0,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_6">
|
||||
<item>@string/stats_custom_layout_average_moving_speed_key</item>
|
||||
<item>1,0,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_7">
|
||||
<item>@string/stats_custom_layout_altitude_key</item>
|
||||
<item>1,0,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_8">
|
||||
<item>@string/stats_custom_layout_gain_key</item>
|
||||
<item>1,0,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_9">
|
||||
<item>@string/stats_custom_layout_loss_key</item>
|
||||
<item>1,0,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_10">
|
||||
<item>@string/stats_custom_layout_pace_key</item>
|
||||
<item>0,0,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_11">
|
||||
<item>@string/stats_custom_layout_average_moving_pace_key</item>
|
||||
<item>0,0,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_12">
|
||||
<item>@string/stats_custom_layout_average_pace_key</item>
|
||||
<item>0,0,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_13">
|
||||
<item>@string/stats_custom_layout_fastest_pace_key</item>
|
||||
<item>0,0,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_14">
|
||||
<item>@string/stats_custom_layout_coordinates_key</item>
|
||||
<item>0,0,1</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_15">
|
||||
<item>@string/stats_custom_layout_heart_rate_key</item>
|
||||
<item>0,0,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_16">
|
||||
<item>@string/stats_custom_layout_cadence_key</item>
|
||||
<item>0,0,0</item>
|
||||
</string-array>
|
||||
<string-array name="stats_custom_layout_fields_default_value_17">
|
||||
<item>@string/stats_custom_layout_power_key</item>
|
||||
<item>0,0,0</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Stats empty values -->
|
||||
<string name="stats_empty_value_integer" translatable="false">0</string>
|
||||
<string name="stats_empty_value_float" translatable="false">0.0</string>
|
||||
<string name="stats_empty_value_time" translatable="false">00:00</string>
|
||||
<string name="stats_empty_value_coordinates" translatable="false">0.0, 0.0</string>
|
||||
|
||||
<!-- Statistics dataFields: columns per row -->
|
||||
<integer-array name="stats_custom_layout_fields_columns_per_row">
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
</integer-array>
|
||||
|
||||
<string name="prefs_last_version_key" translatable="false">lastVersionKey</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -44,6 +44,12 @@
|
||||
true
|
||||
</bool>
|
||||
|
||||
<string name="stats_show_coordinate_key" translatable="false">statsShowCoordinate</string>
|
||||
<bool name="stats_show_coordinate_default" translatable="false">true</bool>
|
||||
|
||||
<string name="stats_show_grade_altitude_key" translatable="false">statsShowGradeElevation</string>
|
||||
<bool name="stats_show_altitude_default" translatable="false">true</bool>
|
||||
|
||||
<!-- <string name="auto_resume_track_timeout_key" translatable="false">autoResumeTrackTimeout</string>-->
|
||||
<!-- <string name="auto_resume_track_timeout_default" translatable="false">10</string>-->
|
||||
<!-- <string name="auto_resume_track_timeout_always" translatable="false">-1</string>-->
|
||||
|
||||
@@ -196,6 +196,8 @@ limitations under the License.
|
||||
<string name="description_time">Time</string>
|
||||
<string name="description_total_distance">Total distance: %1$.2f km (%2$.1f mi)</string>
|
||||
<string name="description_total_time">Total time: %1$s</string>
|
||||
<string name="description_speed_source_sensor">Sensor: %1$s</string>
|
||||
<string name="description_speed_source_gps">GPS</string>
|
||||
<!-- Export -->
|
||||
<string name="export_with_photos">with photos</string>
|
||||
<string name="export_without_photos">without photos</string>
|
||||
@@ -236,6 +238,7 @@ limitations under the License.
|
||||
</plurals>
|
||||
<string name="generic_completed">Completed</string>
|
||||
<string name="generic_click_twice_cancel">Click back again to cancel</string>
|
||||
<string name="generic_choose_an_option">Choose an option</string>
|
||||
<!-- Gps -->
|
||||
<string name="gps_starting">Starting GPS</string>
|
||||
<string name="gps_wait_for_better_signal">Waiting for a better GPS signal</string>
|
||||
@@ -332,6 +335,8 @@ limitations under the License.
|
||||
<string name="settings_chart_by_distance">By distance</string>
|
||||
<string name="settings_chart_by_time">By time</string>
|
||||
<!-- Settings Recording -->
|
||||
<string name="settings_recording_customize_layout_title">Customize your recording layout</string>
|
||||
<string name="settings_recording_customize_layout_select_columns">Data fields per row</string>
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_title">Show statistics on lockscreen</string>
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_summary">While recording, show statistics without unlocking the device.</string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_title">Keep screen on</string>
|
||||
@@ -356,8 +361,15 @@ limitations under the License.
|
||||
<string name="settings_night_mode_option_day">Day</string>
|
||||
<string name="settings_night_mode_option_night">Night</string>
|
||||
|
||||
<!-- Settings Reset -->
|
||||
<!-- Reset -->
|
||||
<string name="settings_reset">Reset</string>
|
||||
<!-- Custom Layout Reset -->
|
||||
<string name="settings_layout_reset">Reset Custom Layout</string>
|
||||
<string name="settings_layout_reset_confirm_message">All layout settings will be reverted to the default values. This will not delete any tracks on the device.</string>
|
||||
<string name="settings_layout_reset_confirm_title">Reset your layouts?</string>
|
||||
<string name="settings_layout_reset_done">All layout settings have been reverted to the default values</string>
|
||||
<!-- Settings Reset -->
|
||||
<string name="settings_all_reset">Reset All Settings</string>
|
||||
<string name="settings_reset_confirm_message">All settings will be reverted to the default values. This will not delete any tracks on the device.</string>
|
||||
<string name="settings_reset_confirm_title">Reset now?</string>
|
||||
<string name="settings_reset_done">All settings have been reverted to the default values</string>
|
||||
@@ -373,7 +385,6 @@ limitations under the License.
|
||||
<string name="settings_sensor_wheel_circumference">Wheel circumference (mm)</string>
|
||||
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_coordinate">Latitude/Longitude</string>
|
||||
<string name="settings_stats_rate_title">Preferred rate</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metric (km, m)</string>
|
||||
@@ -392,6 +403,7 @@ limitations under the License.
|
||||
<string name="stats_fastest_pace">Fastest pace</string>
|
||||
<string name="stats_gain">Gain</string>
|
||||
<string name="stats_loss">Loss</string>
|
||||
<string name="stats_coordinates">Latitude/Longitude</string>
|
||||
<string name="stats_latitude">Latitude</string>
|
||||
<string name="stats_longitude">Longitude</string>
|
||||
<string name="stats_max_speed">Max speed</string>
|
||||
@@ -401,6 +413,9 @@ limitations under the License.
|
||||
<string name="stats_distance">Distance</string>
|
||||
<string name="stats_total_time">Total time</string>
|
||||
<string name="stats_split_interval">Split interval</string>
|
||||
<string name="stats_sensors_heart_rate">Heart Rate</string>
|
||||
<string name="stats_sensors_cadence">Cadence</string>
|
||||
<string name="stats_sensors_power">Power</string>
|
||||
<!-- Track -->
|
||||
<string name="track_name_format">Track %1$d</string>
|
||||
<string name="track_recording_notification_accuracy">Location accuracy: %1$s</string>
|
||||
@@ -436,6 +451,9 @@ limitations under the License.
|
||||
<string name="unit_mile_per_hour">mph</string>
|
||||
<string name="unit_minute_per_kilometer">min/km</string>
|
||||
<string name="unit_minute_per_mile">min/mi</string>
|
||||
<!-- Location Coordinates -->
|
||||
<string name="location_coordinate">%1$s°</string>
|
||||
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
||||
<!-- Value -->
|
||||
<string name="value_adapt_accuracy">Adapt for accuracy</string>
|
||||
<string name="value_adapt_battery_life">Adapt for battery life</string>
|
||||
@@ -553,4 +571,9 @@ limitations under the License.
|
||||
<string name="instant_export_enabled_title">Instant post-workout export</string>
|
||||
|
||||
<string name="settings_default_export_uri_title">Track export directory</string>
|
||||
<string name="title_activity_settings_custom_layout">Customize Recording Layout</string>
|
||||
<!-- Fields -->
|
||||
<string name="field_set_primary">Primary</string>
|
||||
<string name="field_set_secondary">Secondary</string>
|
||||
<string name="field_remove_from_layout">Remove</string>
|
||||
</resources>
|
||||
|
||||
@@ -100,12 +100,16 @@ limitations under the License.
|
||||
</style>
|
||||
<!-- sensor state value -->
|
||||
<!-- stats horizontal line -->
|
||||
<style name="StatsHorizontalLine">
|
||||
<style name="HorizontalLine">
|
||||
<item name="android:background">@color/stats_separator</item>
|
||||
<item name="android:layout_height">1dp</item>
|
||||
<item name="android:layout_margin">8dp</item>
|
||||
<item name="android:layout_marginTop">8dp</item>
|
||||
<item name="android:layout_marginBottom">8dp</item>
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
</style>
|
||||
<style name="StatsHorizontalLine" parent="HorizontalLine">
|
||||
<item name="android:layout_margin">8dp</item>
|
||||
</style>
|
||||
<!-- stats large item container -->
|
||||
<style name="StatsLargeItemContainer">
|
||||
<item name="android:layout_gravity">center</item>
|
||||
@@ -121,6 +125,7 @@ limitations under the License.
|
||||
<item name="android:textAllCaps">true</item>
|
||||
<item name="android:textColor">?attr/colorAccent</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:layout_marginTop">8dp</item>
|
||||
</style>
|
||||
|
||||
<style name="IntervalLabel" parent="@style/LargeLabel">
|
||||
@@ -133,26 +138,54 @@ limitations under the License.
|
||||
<item name="android:layout_gravity">center</item>
|
||||
</style>
|
||||
|
||||
<style name="StatsLargeLabel" parent="@style/LargeLabel">
|
||||
<style name="StatsPrimaryLabel" parent="@style/TextMicro">
|
||||
<item name="android:gravity">center</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
<item name="android:singleLine">false</item>
|
||||
<item name="android:textAllCaps">true</item>
|
||||
<item name="android:textColor">?attr/colorAccent</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:paddingLeft">4dp</item>
|
||||
<item name="android:paddingRight">4dp</item>
|
||||
<item name="android:layout_marginTop">8dp</item>
|
||||
<item name="android:textStyle">normal</item>
|
||||
</style>
|
||||
|
||||
<style name="StatsSecondaryLabel" parent="@style/TextMicro">
|
||||
<item name="android:gravity">center</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
<item name="android:singleLine">false</item>
|
||||
<item name="android:textAllCaps">true</item>
|
||||
<item name="android:paddingLeft">4dp</item>
|
||||
<item name="android:paddingRight">4dp</item>
|
||||
<item name="android:layout_marginTop">16dp</item>
|
||||
<item name="android:textStyle">normal</item>
|
||||
</style>
|
||||
|
||||
<style name="StatsLargeValue" parent="@style/TextLarge">
|
||||
<style name="StatsLabelHidden" parent="@style/StatsPrimaryLabel">
|
||||
<item name="android:textColor">@android:color/secondary_text_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="StatsPrimaryValue" parent="@style/TextLarge">
|
||||
<item name="android:layout_gravity">center</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
<item name="android:textSize">32sp</item>
|
||||
<item name="android:textSize">36sp</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="StatsSecondaryValue" parent="@style/TextLarge">
|
||||
<item name="android:layout_gravity">center</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
<item name="android:textSize">28sp</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="StatsValueHidden" parent="@style/StatsPrimaryValue">
|
||||
<item name="android:textColor">@android:color/secondary_text_dark</item>
|
||||
</style>
|
||||
<!-- stats large value container -->
|
||||
<style name="StatsLargeValueContainer">
|
||||
<item name="android:layout_gravity">center</item>
|
||||
@@ -290,4 +323,14 @@ limitations under the License.
|
||||
</style>
|
||||
|
||||
<style name="ProgressBar" parent="@style/Widget.AppCompat.ProgressBar.Horizontal" />
|
||||
|
||||
<!-- Card View for stats -->
|
||||
<style name="StatsCardView">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="cardElevation">1dp</item>
|
||||
<item name="cardMaxElevation">1dp</item>
|
||||
<item name="cardPreventCornerOverlap">true</item>
|
||||
<item name="cardUseCompatPadding">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -43,14 +43,9 @@ limitations under the License.
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_show">
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="@bool/stats_show_altitude_default"
|
||||
android:key="@string/stats_show_grade_altitude_key"
|
||||
android:title="@string/stats_altitude" />
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="@bool/stats_show_coordinate_default"
|
||||
android:key="@string/stats_show_coordinate_key"
|
||||
android:title="@string/settings_stats_coordinate" />
|
||||
<Preference
|
||||
app:key="@string/stats_custom_layout_key"
|
||||
app:title="@string/settings_recording_customize_layout_title" />
|
||||
<ListPreference
|
||||
android:defaultValue="@string/night_mode_default"
|
||||
android:entries="@array/night_mode_options"
|
||||
@@ -193,6 +188,14 @@ limitations under the License.
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_reset">
|
||||
<de.dennisguse.opentracks.settings.ResetDialogPreference
|
||||
android:dialogMessage="@string/settings_layout_reset_confirm_message"
|
||||
android:dialogTitle="@string/settings_layout_reset_confirm_title"
|
||||
android:key="@string/settings_layout_reset_key"
|
||||
android:negativeButtonText="@android:string/cancel"
|
||||
android:persistent="false"
|
||||
android:positiveButtonText="@android:string/ok"
|
||||
android:title="@string/settings_layout_reset" />
|
||||
<de.dennisguse.opentracks.settings.ResetDialogPreference
|
||||
android:dialogMessage="@string/settings_reset_confirm_message"
|
||||
android:dialogTitle="@string/settings_reset_confirm_title"
|
||||
@@ -200,7 +203,7 @@ limitations under the License.
|
||||
android:negativeButtonText="@android:string/cancel"
|
||||
android:persistent="false"
|
||||
android:positiveButtonText="@android:string/ok"
|
||||
android:title="@string/settings_reset" />
|
||||
android:title="@string/settings_all_reset" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/about_preference_title">
|
||||
|
||||
Reference in New Issue
Block a user