Compare commits

...

23 Commits

Author SHA1 Message Date
Román Ginés cb71c40350 Release: v3.26.1 2022-03-22 13:37:35 +01:00
Román Ginés 22bde4af3c Bug fixed: contextual action bar didn't overlap toolbar in night theme. 2022-03-21 12:59:45 +01:00
Dennis Guse ecfcc180b9 Release: v3.26.0 2022-03-14 23:21:17 +01:00
mondstern 9c1af67e7e Translated using Weblate (Czech)
Currently translated at 99.4% (499 of 502 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2022-03-14 18:52:57 +01:00
mondstern 873422540f Translated using Weblate (Vietnamese)
Currently translated at 100.0% (502 of 502 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2022-03-13 01:28:18 +01:00
vsevjednom-cz 6cf0b26058 Translated using Weblate (Czech)
Currently translated at 95.0% (477 of 502 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2022-03-13 01:28:18 +01:00
Quang Trung a94b227767 Translated using Weblate (Vietnamese)
Currently translated at 96.8% (486 of 502 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2022-03-13 01:28:17 +01:00
FTno c820a76102 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (502 of 502 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2022-03-13 01:28:17 +01:00
Peter Storch e61b065f2b fix #1139 CI build
- fix NPEs in TrackPointCreator.fill()
- fix locale dependant tests
- wait for UI components
- extract utility functions into EspressoUtils
2022-03-13 01:28:10 +01:00
dependabot[bot] 24d8e976da Bump mockito-android from 4.3.1 to 4.4.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 4.3.1 to 4.4.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v4.3.1...v4.4.0)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-android
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-09 11:57:38 +01:00
Román Ginés 79e45f7aea Migration to Material Components theme and style.
New night theme mainly white on black.
Fixes #1122.
Fixes #957.
2022-03-03 10:02:34 +01:00
J. Lavoie 72f7b12da6 Translated using Weblate (French)
Currently translated at 87.6% (71 of 81 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/app-store-metadata-files/fr/
2022-03-03 00:19:26 +01:00
Mario 9973dd05bd Translated using Weblate (Spanish)
Currently translated at 100.0% (490 of 490 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es/
2022-03-03 00:19:26 +01:00
Mario 41072a8981 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (490 of 490 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es_419/
2022-03-03 00:19:26 +01:00
dependabot[bot] b1dcdf326c Bump gradle from 7.0.4 to 7.1.2
Bumps gradle from 7.0.4 to 7.1.2.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-03 00:19:23 +01:00
Dennis Guse c271dd32ac Merge pull request #1132 from spacecowboy/voiceHeartRate
Voice announcements: configurable, TtsSpan, and heart rate.
2022-03-02 15:17:22 +01:00
dependabot[bot] 5dfa867034 Bump actions/checkout from 2.4.0 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.4.0 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.4.0...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-02 15:01:08 +01:00
Román Ginés ada0f88538 Time on list of tracks uses 24 hour format. Fixes #1140. 2022-03-02 08:36:14 +01:00
Dennis Guse 4df0e2a65c Release: v3.25.1 2022-03-02 03:36:43 +01:00
Jonas Kalderstam 9e7e8c60a6 Voice announcement: added TtsSpans to announcements
Fixes #876
2022-03-01 10:09:00 +01:00
Jonas Kalderstam 1075021e21 Voice announcement: added heart rate info to voice announcements
Also added setting to toggle announcement of heart rate

Defaults to off to preserve current behavior
2022-03-01 09:58:29 +01:00
Peter Storch b5acfa022d try appCtx to persist NextCloud URI permission
for #1080
Also fixing a missing break in a switch statement.
2022-02-26 23:42:50 +01:00
dependabot[bot] 6fe38453b3 Bump gradle from 7.1.1 to 7.1.2
Bumps gradle from 7.1.1 to 7.1.2.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-25 12:54:54 +01:00
90 changed files with 1609 additions and 1315 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2.4.0
uses: actions/checkout@v3
- uses: actions/setup-java@v2
with:
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2.4.0
uses: actions/checkout@v3
- uses: actions/setup-java@v2
with:
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
fail-fast: false
steps:
- name: checkout
uses: actions/checkout@v2.4.0
uses: actions/checkout@v3
- uses: actions/setup-java@v2
with:
+1
View File
@@ -42,6 +42,7 @@ captures/
.idea/dictionaries
.idea/libraries
.idea/caches
.idea/misc.xml
# IntelliJ IDEA
-24
View File
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="src/main/res/drawable/ic_activity_bike_24dp.xml" value="0.1765" />
<entry key="src/main/res/drawable/ic_activity_sailing_24dp.xml" value="0.1935" />
<entry key="src/main/res/drawable/ic_activity_snowboarding_24dp.xml" value="0.1935" />
<entry key="src/main/res/drawable/ic_activity_swimming_24dp.xml" value="0.1935" />
<entry key="src/main/res/drawable/ic_activity_swimming_open_24dp.xml" value="0.1935" />
<entry key="src/main/res/drawable/ic_activity_workout_24dp.xml" value="0.1935" />
<entry key="src/main/res/menu/aggregated_statistics.xml" value="0.10208333333333333" />
<entry key="src/main/res/xml/settings_gps.xml" value="0.22604166666666667" />
<entry key="src/main/res/xml/settings_sensors.xml" value="0.2101851851851852" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
+4 -4
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.1'
classpath 'com.android.tools.build:gradle:7.1.2'
}
}
@@ -59,8 +59,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 4752
versionName "v3.25.0"
versionCode 4775
versionName "v3.26.1"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -130,7 +130,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:4.3.1'
androidTestImplementation 'org.mockito:mockito-android:4.4.0'
androidTestUtil 'androidx.test:orchestrator:1.4.1'
}
@@ -0,0 +1,6 @@
v3.25.1: OpenTracks
Bugfix:
- Automatic export: hopefully prevent silent failures
- Automatic export: prevent crash
@@ -0,0 +1,8 @@
v3.26.0: OpenTracks
Changes:
- Voice announcements are configurable
- Voice announcements of heart rate
Bugfix:
- Crash due to race condition
@@ -0,0 +1,4 @@
v3.26.1: OpenTracks
Bugfix:
- Contextual action bar didn't overlap toolbar in night theme.
@@ -1 +1 @@
OpenTracks - Suivi d'activités sportives respectueux de votre vie privée.
Appli de suivi des activités sportives respectueuse de votre vie privée.
@@ -13,22 +13,20 @@ 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 de.dennisguse.opentracks.util.EspressoUtils.childAtPosition;
import static de.dennisguse.opentracks.util.EspressoUtils.veryLongTouch;
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
import static de.dennisguse.opentracks.util.EspressoUtils.withListSize;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ListView;
import androidx.test.espresso.Espresso;
import androidx.test.espresso.IdlingRegistry;
import androidx.test.espresso.IdlingResource;
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.assertion.ViewAssertions;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -37,7 +35,6 @@ 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.After;
import org.junit.Before;
@@ -65,12 +62,12 @@ public class EspressoDeleteTrackTest {
Instrumentation instrumentation
= InstrumentationRegistry.getInstrumentation();
idlingResource = new MyIdlingResource(instrumentation.getTargetContext());
Espresso.registerIdlingResources(idlingResource);
IdlingRegistry.getInstance().register(idlingResource);
}
@After
public void unregisterIntentServiceIdlingResource() {
Espresso.unregisterIdlingResources(idlingResource);
IdlingRegistry.getInstance().unregister(idlingResource);
}
@Ignore("Test fails permanently")
@@ -140,87 +137,6 @@ public class EspressoDeleteTrackTest {
onView(withId(R.id.track_list)).check(ViewAssertions.matches(withListSize(countBefore - 1)));
}
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));
}
};
}
public static Matcher<View> withListSize (final int size) {
return new TypeSafeMatcher<>() {
@Override
public boolean matchesSafely(final View view) {
return ((ListView) view).getCount() == size;
}
@Override
public void describeTo(final Description description) {
description.appendText("ListView should have " + size + " items");
}
};
}
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);
}
};
}
private int numberOfItemsListView() {
final int[] counts = new int[1];
@@ -2,52 +2,43 @@ package de.dennisguse.opentracks;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.Espresso.openContextualActionModeOverflowMenu;
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 static de.dennisguse.opentracks.util.EspressoUtils.childAtPosition;
import static de.dennisguse.opentracks.util.EspressoUtils.veryLongTouch;
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
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.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.GrantPermissionRule;
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;
import java.util.Locale;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class EspressoEditTrackRecordingTest {
@Rule
public final LocaleRule mLocaleRule = new LocaleRule(Locale.ENGLISH);
@Rule
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
@@ -71,26 +62,10 @@ public class EspressoEditTrackRecordingTest {
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());
openContextualActionModeOverflowMenu();
// 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());
// Click the item.
onView(withText(R.string.menu_edit)).perform(click());
// change name for "New Name"
ViewInteraction textInputEditText = onView(
@@ -147,112 +122,4 @@ public class EspressoEditTrackRecordingTest {
}
}
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));
}
};
}
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);
}
};
}
}
@@ -1,5 +1,15 @@
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.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.Matchers.anything;
import static de.dennisguse.opentracks.util.EspressoUtils.selectTabAtIndex;
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
import android.view.MotionEvent;
import android.view.View;
@@ -12,21 +22,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.GrantPermissionRule;
import com.google.android.material.tabs.TabLayout;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
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.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.Matchers.anything;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class EspressoUITest {
@@ -136,43 +136,4 @@ public class EspressoUITest {
};
}
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 selectTabAtIndex(final int index) {
return new ViewAction() {
@Override
public String getDescription() {
return "Selecting tab.";
}
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
}
@Override
public void perform(UiController uiController, View view) {
TabLayout tabLayout = (TabLayout) view;
tabLayout.getTabAt(index).select();
}
};
}
}
@@ -0,0 +1,55 @@
package de.dennisguse.opentracks;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import androidx.test.core.app.ApplicationProvider;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.util.Locale;
public class LocaleRule implements TestRule {
private final Locale[] mLocales;
private Locale mDeviceLocale;
public LocaleRule(Locale... locales) {
assert locales != null && locales.length > 0;
mLocales = locales;
}
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
try {
mDeviceLocale = Locale.getDefault();
for (Locale locale : mLocales) {
setLocale(locale);
base.evaluate();
}
} finally {
if (mDeviceLocale != null) {
setLocale(mDeviceLocale);
}
}
}
};
}
private void setLocale(Locale locale) {
Resources resources = ApplicationProvider.getApplicationContext().getResources();
Locale.setDefault(locale);
Configuration config = resources.getConfiguration();
config.setLocale(locale);
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
resources.updateConfiguration(config, displayMetrics);
}
}
@@ -125,7 +125,7 @@ public class TestDataUtil {
trackPoint.setAltitude(i * ALTITUDE_INTERVAL);
trackPoint.setSpeed(Speed.of(5f + (i / 10f)));
trackPoint.setHeartRate(100f + i);
trackPoint.setHeartRate(100f + i % 80);
trackPoint.setCadence(300f + i);
trackPoint.setPower(400f + i);
trackPoint.setAltitudeGain(ALTITUDE_GAIN);
@@ -215,7 +215,7 @@ public class TrackRecordingServiceTestStateMachine {
// when
service.pauseCurrentTrack();
Thread.sleep(1000);
Thread.sleep(2000);
// then
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
@@ -8,32 +8,40 @@ import android.util.Pair;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
import java.util.Locale;
import de.dennisguse.opentracks.LocaleRule;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.ui.intervals.IntervalStatistics;
import de.dennisguse.opentracks.util.StringUtils;
@RunWith(AndroidJUnit4.class)
public class VoiceAnnouncementUtilsTest {
@Rule
public final LocaleRule mLocaleRule = new LocaleRule(Locale.ENGLISH);
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
@Before
public void setUp() {
contentProviderUtils = new ContentProviderUtils(context);
PreferencesUtils.setVoiceAnnounceHeartRate(false);
}
@Test
@@ -46,10 +54,10 @@ public class VoiceAnnouncementUtilsTest {
stats.setTotalAltitudeGain(6000f);
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, null);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, null, null).toString();
// then
assertEquals("total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 18.4 kilometers per hour", announcement);
assertEquals("Total distance 20.00 kilometers. 1 hour 5 minutes 10 seconds. Speed 18.4 kilometers per hour.", announcement);
}
@Test
@@ -63,22 +71,15 @@ public class VoiceAnnouncementUtilsTest {
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
assertEquals(trackPointIterator.getCount(), numberOfPoints);
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000));
intervalStatistics.addTrackPoints(trackPointIterator);
lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
}
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval, null).toString();
// then
Assert.assertEquals(
"total distance " +
StringUtils.getDistanceParts(context, stats.getTotalDistance(), true).first +
" kilometers in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
StringUtils.getSpeedParts(context, stats.getAverageMovingSpeed(), true, true).first +
" kilometers per hour Lap speed of " +
StringUtils.getSpeedParts(context, lastInterval.getSpeed(), true, true).first +
" kilometers per hour",
announcement);
assertEquals("Total distance 14.21 kilometers. 16 minutes 39 seconds. Speed 51.2 kilometers per hour. Lap speed 51.2 kilometers per hour.", announcement);
}
@Test
@@ -91,10 +92,10 @@ public class VoiceAnnouncementUtilsTest {
stats.setTotalAltitudeGain(6000f);
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, false, null);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, false, null, null).toString();
// then
assertEquals("total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 3 minutes 15 seconds per kilometer", announcement);
assertEquals("Total distance 20.00 kilometers. 1 hour 5 minutes 10 seconds. Pace 3 minutes 15 seconds per kilometer.", announcement);
}
@Test
@@ -108,22 +109,15 @@ public class VoiceAnnouncementUtilsTest {
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
assertEquals(trackPointIterator.getCount(), numberOfPoints);
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000));
intervalStatistics.addTrackPoints(trackPointIterator);
lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
}
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, false, lastInterval);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, false, lastInterval, null).toString();
// then
assertEquals(
"total distance " +
StringUtils.getDistanceParts(context, stats.getTotalDistance(), true).first +
" kilometers in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
buildAndGetTimeText(stats.getAverageMovingSpeed().toPace(true), true) +
" per kilometer Lap time of " +
buildAndGetTimeText(lastInterval.getSpeed().toPace(true), true) +
" per kilometer",
announcement);
assertEquals("Total distance 14.21 kilometers. 16 minutes 39 seconds. Pace 1 minute 10 seconds per kilometer. Lap time 1 minute 10 seconds per kilometer.", announcement);
}
@Test
@@ -136,10 +130,10 @@ public class VoiceAnnouncementUtilsTest {
stats.setTotalAltitudeGain(6000f);
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, true, null);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, true, null, null).toString();
// then
assertEquals("total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 11.4 miles per hour", announcement);
assertEquals("Total distance 12.43 miles. 1 hour 5 minutes 10 seconds. Speed 11.4 miles per hour.", announcement);
}
@Test
@@ -153,22 +147,15 @@ public class VoiceAnnouncementUtilsTest {
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
assertEquals(trackPointIterator.getCount(), numberOfPoints);
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000));
intervalStatistics.addTrackPoints(trackPointIterator);
lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
}
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, true, lastInterval);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, true, lastInterval, null).toString();
// then
assertEquals(
"total distance " +
StringUtils.getDistanceParts(context, stats.getTotalDistance(), false).first +
" miles in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
StringUtils.getSpeedParts(context, stats.getAverageMovingSpeed(), false, true).first +
" miles per hour Lap speed of " +
StringUtils.getSpeedParts(context, lastInterval.getSpeed(), false, true).first +
" miles per hour",
announcement);
assertEquals("Total distance 8.83 miles. 16 minutes 39 seconds. Speed 31.8 miles per hour. Lap speed 31.8 miles per hour.", announcement);
}
@Test
@@ -181,10 +168,10 @@ public class VoiceAnnouncementUtilsTest {
stats.setTotalAltitudeGain(6000f);
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, false, null);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, false, null, null).toString();
// then
assertEquals("total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 5 minutes 15 seconds per mile", announcement);
assertEquals("Total distance 12.43 miles. 1 hour 5 minutes 10 seconds. Pace 5 minutes 15 seconds per mile.", announcement);
}
@Test
@@ -198,51 +185,39 @@ public class VoiceAnnouncementUtilsTest {
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
assertEquals(trackPointIterator.getCount(), numberOfPoints);
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000));
intervalStatistics.addTrackPoints(trackPointIterator);
lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
}
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, false, lastInterval);
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, false, false, lastInterval, null).toString();
// then
//assertEquals("total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 5 minutes 15 seconds per mile Lap time of 1 minute 53 seconds per mile", announcement);
assertEquals(
"total distance " +
StringUtils.getDistanceParts(context, stats.getTotalDistance(), false).first +
" miles in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
buildAndGetTimeText(stats.getAverageMovingSpeed().toPace(false), true) +
" per mile Lap time of " +
buildAndGetTimeText(lastInterval.getSpeed().toPace(false), true) +
" per mile",
announcement);
assertEquals("Total distance 8.83 miles. 16 minutes 39 seconds. Pace 1 minute 53 seconds per mile. Lap time 1 minute 53 seconds per mile.", announcement);
}
/**
* Builds an returns the text representing the duration's time.
*
* @param duration Duration object.
* @param showFromMinutes show minutes tough it's 0.
* @return text representing the duratin's time.
*/
private String buildAndGetTimeText(Duration duration, boolean showFromMinutes) {
long hours = Math.abs(duration.getSeconds()) / 3600;
long minutes = (Math.abs(duration.getSeconds()) % 3600) / 60;
long seconds = Math.abs(duration.getSeconds()) % 60;
String hUnit = hours > 1 || hours == 0 ? "hours" : "hour";
String mUnit = minutes > 1 || minutes == 0 ? "minutes" : "minute";
String sUnit = seconds > 1 || seconds == 0 ? "seconds" : "second";
@Test
public void getAnnouncement_heart_rate_and_sensor_statistics() {
PreferencesUtils.setVoiceAnnounceHeartRate(true);
String res = hours > 0 ? hours + " " + hUnit : "";
if (hours > 0) {
res += minutes > 0 || showFromMinutes ? " " + minutes + " " + mUnit : "";
} else {
res += minutes > 0 || showFromMinutes ? minutes + " " + mUnit : "";
int numberOfPoints = 1000;
Pair<Track.Id, TrackStatistics> trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints);
Track.Id trackId = trackWithStats.first;
TrackStatistics stats = trackWithStats.second;
IntervalStatistics.Interval lastInterval;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
assertEquals(trackPointIterator.getCount(), numberOfPoints);
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000));
intervalStatistics.addTrackPoints(trackPointIterator);
lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
}
if (hours > 0 || minutes > 0 || showFromMinutes) {
res += " " + seconds + " " + sUnit;
} else {
res += seconds + " " + sUnit;
}
return res;
SensorStatistics sensorStatistics = new SensorStatistics(HeartRate.of(180f), HeartRate.of(180f), null, null, null);
// when
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval, sensorStatistics).toString();
// then
assertEquals("Total distance 14.21 kilometers. 16 minutes 39 seconds. Speed 51.2 kilometers per hour. Lap speed 51.2 kilometers per hour. Average heart rate 180 bpm. Current heart rate 133 bpm.", announcement);
}
}
}
@@ -24,6 +24,7 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,7 +32,9 @@ import java.time.Duration;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.Locale;
import de.dennisguse.opentracks.LocaleRule;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
@@ -47,6 +50,9 @@ import de.dennisguse.opentracks.util.StringUtils;
@RunWith(AndroidJUnit4.class)
public class DescriptionGeneratorTest {
@Rule
public final LocaleRule mLocaleRule = new LocaleRule(Locale.ENGLISH);
private static final Instant START_TIME = Instant.ofEpochMilli(1288721514000L);
private DescriptionGenerator descriptionGenerator;
@@ -28,6 +28,7 @@ import java.time.Duration;
import java.time.Instant;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
/**
@@ -67,6 +68,7 @@ public class TrackStatisticsTest {
assertEquals(0.0, statistics.getMaxSpeed().toMPS(), 0.0);
assertEquals(0.0, statistics.getAverageSpeed().toMPS(), 0.0);
assertEquals(0.0, statistics.getAverageMovingSpeed().toMPS(), 0.0);
assertNull(statistics.getAverageHeartRate());
}
@Test
@@ -91,6 +93,8 @@ public class TrackStatisticsTest {
statistics.setMinAltitude(1200.0); // Resulting min altitude
statistics2.setMaxAltitude(3575.0); // Resulting max altitude
statistics2.setMinAltitude(2800.0);
statistics.setAverageHeartRate(HeartRate.of(100f));
statistics2.setAverageHeartRate(HeartRate.of(200f));
// when
statistics.merge(statistics2);
@@ -105,6 +109,7 @@ public class TrackStatisticsTest {
assertEquals(Speed.of(statistics.getTotalDistance(), statistics.getMovingTime()).toMPS(), statistics.getMaxSpeed().toMPS(), 0.001);
assertEquals(1200.0, statistics.getMinAltitude(), 0.001);
assertEquals(3575.0, statistics.getMaxAltitude(), 0.001);
assertEquals(150.0, statistics.getAverageHeartRate().getBPM(), 0.001);
}
@Test
@@ -40,6 +40,7 @@ public class TrackStatisticsUpdaterTest {
assertNull(statistics.getTotalAltitudeGain());
assertNull(statistics.getTotalAltitudeLoss());
assertNull(statistics.getAverageHeartRate());
}
@Test
@@ -66,6 +67,7 @@ public class TrackStatisticsUpdaterTest {
assertNull(statistics.getTotalAltitudeGain());
assertNull(statistics.getTotalAltitudeLoss());
assertNull(statistics.getAverageHeartRate());
}
@Test
@@ -91,6 +93,7 @@ public class TrackStatisticsUpdaterTest {
assertEquals(14.226, statistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(14.226, statistics.getAverageMovingSpeed().toMPS(), 0.01);
assertEquals(9.84, statistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(106.85f, statistics.getAverageHeartRate().getBPM(), 0.01);
}
@Test
@@ -9,6 +9,7 @@ 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 de.dennisguse.opentracks.util.EspressoUtils.waitFor;
import android.util.Pair;
import android.view.View;
@@ -73,7 +74,7 @@ public class EspressoAggregatedFilterTest {
withParent(allOf(withId(R.id.filter_items),
withParent(IsInstanceOf.<View>instanceOf(android.view.ViewGroup.class)))),
isDisplayed()));
checkBox.check(matches(isDisplayed()));
checkBox.perform(waitFor(2000));
// check there's an edit text for "from date"
onView(withId(R.id.filter_date_edit_text_from)).check(matches(isDisplayed()));
@@ -81,4 +82,5 @@ public class EspressoAggregatedFilterTest {
// check there's an edit text for "to date"
onView(withId(R.id.filter_date_edit_text_to)).check(matches(isDisplayed()));
}
}
@@ -4,31 +4,22 @@ package de.dennisguse.opentracks.ui.customRecordingLayout;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.CoreMatchers.is;
import static de.dennisguse.opentracks.util.EspressoUtils.veryLongTouch;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import androidx.recyclerview.widget.RecyclerView;
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.espresso.matcher.ViewMatchers;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
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.runner.RunWith;
@@ -82,51 +73,4 @@ public class EspressoCustomLayoutTest {
}
}
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 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,173 @@
package de.dennisguse.opentracks.util;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.util.TreeIterables.breadthFirstViewTraversal;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ListView;
import androidx.test.espresso.PerformException;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.action.MotionEvents;
import androidx.test.espresso.util.HumanReadables;
import com.google.android.material.tabs.TabLayout;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import java.util.concurrent.TimeoutException;
public class EspressoUtils {
public 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));
}
};
}
public static Matcher<View> withListSize (final int size) {
return new TypeSafeMatcher<>() {
@Override
public boolean matchesSafely(final View view) {
return ((ListView) view).getCount() == size;
}
@Override
public void describeTo(final Description description) {
description.appendText("ListView should have " + size + " items");
}
};
}
public 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);
}
};
}
public 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);
}
};
}
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 : 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();
}
};
}
public static ViewAction selectTabAtIndex(final int index) {
return new ViewAction() {
@Override
public String getDescription() {
return "Selecting tab.";
}
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
}
@Override
public void perform(UiController uiController, View view) {
TabLayout tabLayout = (TabLayout) view;
tabLayout.getTabAt(index).select();
}
};
}
}
@@ -24,6 +24,7 @@ import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,6 +46,7 @@ import java.util.Locale;
import java.util.TimeZone;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.LocaleRule;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
@@ -56,6 +58,9 @@ import de.dennisguse.opentracks.data.models.Speed;
@RunWith(AndroidJUnit4.class)
public class StringUtilsTest {
@Rule
public final LocaleRule mLocaleRule = new LocaleRule(Locale.ENGLISH);
private final Context context = ApplicationProvider.getApplicationContext();
/**
@@ -23,11 +23,14 @@ import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.OffsetDateTime;
import java.util.Locale;
import de.dennisguse.opentracks.LocaleRule;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@@ -40,6 +43,9 @@ import de.dennisguse.opentracks.settings.PreferencesUtils;
@RunWith(AndroidJUnit4.class)
public class TrackNameUtilsTest {
@Rule
public final LocaleRule mLocaleRule = new LocaleRule(Locale.ENGLISH);
private static final Track.Id TRACK_ID = new Track.Id(1L);
private static final OffsetDateTime START_TIME = OffsetDateTime.parse("2022-01-02T10:15:30.1234+01:00");
@@ -120,10 +120,10 @@ public class StatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
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.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
this.unit.setText(statisticData.getUnit());
this.descMain.setText(statisticData.getField().getTitle());
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.StatsPrimaryLabel : R.style.StatsSecondaryLabel);
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
if (statisticData.hasDescription()) {
this.descSecondary.setVisibility(View.VISIBLE);
this.descSecondary.setText(statisticData.getDescription());
@@ -147,8 +147,8 @@ public class StatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
if (statisticData == null) {
return;
}
this.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.StatsPrimaryValue : R.style.StatsSecondaryValue);
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.StatsPrimaryLabel : R.style.StatsSecondaryLabel);
this.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
}
}
@@ -78,7 +78,7 @@ class ChooseActivityTypeImageAdapter extends BaseAdapter {
}
if (position == selected) {
imageView.setBackgroundColor(ContextCompat.getColor(parent.getContext(), R.color.colorAccent));
imageView.setBackgroundColor(ContextCompat.getColor(parent.getContext(), R.color.opentracks_secondary_color));
} else {
imageView.setBackgroundColor(Color.TRANSPARENT);
}
@@ -32,8 +32,10 @@ public class ExportServiceResultReceiver extends ResultReceiver {
switch (resultCode) {
case RESULT_CODE_SUCCESS:
receiver.onExportSuccess(trackId);
break;
case RESULT_CODE_ERROR:
receiver.onExportError(trackId);
break;
default:
throw new RuntimeException("Unknown resultCode.");
}
@@ -54,10 +54,7 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
protected void onActivityResultCustom(@NonNull Intent resultData) {
Uri directoryUri = resultData.getData();
int takeFlags = resultData.getFlags();
takeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(directoryUri, takeFlags);
IntentUtils.persistDirectoryAccessPermission(this, directoryUri, resultData.getFlags());
startActivity(createNextActivityIntent(directoryUri));
}
@@ -111,11 +108,11 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
Uri oldDirectoryUri = PreferencesUtils.getDefaultExportDirectoryUri();
Uri newDirectoryUri = resultData.getData();
if (oldDirectoryUri != null && !newDirectoryUri.equals(oldDirectoryUri)) {
IntentUtils.releaseDirectoryAccessPermission(getApplicationContext(), oldDirectoryUri);
IntentUtils.releaseDirectoryAccessPermission(this, oldDirectoryUri);
}
PreferencesUtils.setDefaultExportDirectoryUri(newDirectoryUri);
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), newDirectoryUri, resultData.getFlags());
IntentUtils.persistDirectoryAccessPermission(this, newDirectoryUri, resultData.getFlags());
}
@Override
@@ -21,6 +21,7 @@ import android.media.AudioManager;
import android.media.MediaPlayer;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.text.Spannable;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -34,6 +35,7 @@ import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.ui.intervals.IntervalStatistics;
@@ -171,8 +173,12 @@ public class VoiceAnnouncement {
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.one(isMetricUnits));
intervalStatistics.addTrackPoints(trackPointIterator);
IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval();
SensorStatistics sensorStatistics = null;
if (track.getId() != null) {
sensorStatistics = contentProviderUtils.getSensorStats(track.getId());
}
String announcement = VoiceAnnouncementUtils.getAnnouncement(context, track.getTrackStatistics(), isMetricUnits, isReportSpeed, lastInterval);
Spannable announcement = VoiceAnnouncementUtils.getAnnouncement(context, track.getTrackStatistics(), isMetricUnits, isReportSpeed, lastInterval, sensorStatistics);
// We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called.
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used");
@@ -1,7 +1,14 @@
package de.dennisguse.opentracks.services.announcement;
import android.content.Context;
import static android.text.Spanned.SPAN_INCLUSIVE_EXCLUSIVE;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceHeartRate;
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.TtsSpan;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.time.Duration;
@@ -9,6 +16,7 @@ import java.time.Duration;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.ui.intervals.IntervalStatistics;
@@ -17,68 +25,149 @@ class VoiceAnnouncementUtils {
private VoiceAnnouncementUtils() {
}
static String getAnnouncement(Context context, TrackStatistics trackStatistics, boolean isMetricUnits, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval) {
static Spannable getAnnouncement(Context context, TrackStatistics trackStatistics, boolean isMetricUnits, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval, @Nullable SensorStatistics sensorStatistics) {
SpannableStringBuilder builder = new SpannableStringBuilder();
Distance distance = trackStatistics.getTotalDistance();
Speed distancePerTime = trackStatistics.getAverageMovingSpeed();
Speed currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed() : null;
int perUnitStringId = isMetricUnits ? R.string.voice_per_kilometer : R.string.voice_per_mile;
int totalDistanceId = isMetricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
double distanceInUnit = distance.toKM_Miles(isMetricUnits);
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distanceInUnit), distanceInUnit);
int distanceId = isMetricUnits ? R.plurals.voiceDistanceKilometers : R.plurals.voiceDistanceMiles;
builder.append(context.getString(R.string.total_distance));
long distanceIntegerPart = (long) distanceInUnit;
// Extract the decimal part
String distanceFractionalPart = String.format("%.2f", (distanceInUnit - distanceIntegerPart)).substring(2);
// Units should always be english singular for TTS.
// See https://developer.android.com/reference/android/text/style/TtsSpan?hl=en#TYPE_MEASURE
appendDecimalUnit(builder, context.getResources().getQuantityString(distanceId, getQuantityCount(distanceInUnit), distanceInUnit), distanceIntegerPart, distanceFractionalPart, isMetricUnits ? "kilometer" : "mile");
// Punctuation helps introduce natural pauses in TTS
builder.append(".");
if (distance.isZero()) {
return totalDistance;
return builder;
}
// Announce time
Duration movingTime = trackStatistics.getMovingTime();
if (!movingTime.isZero()) {
appendDuration(context, builder, movingTime);
builder.append(".");
}
String rate;
String currentRate;
String currentRateMsg;
if (isReportSpeed) {
int speedId = isMetricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
double speedInUnit = distancePerTime.to(isMetricUnits);
rate = context.getResources().getQuantityString(speedId, getQuantityCount(speedInUnit), speedInUnit);
double currentDistancePerTimeInUnit = currentDistancePerTime != null ? currentDistancePerTime.to(isMetricUnits) : 0;
currentRate = context.getResources().getQuantityString(speedId, getQuantityCount(currentDistancePerTimeInUnit), currentDistancePerTimeInUnit);
currentRateMsg = context.getString(R.string.voice_speed_lap, currentRate);
builder.append(" ")
.append(context.getString(R.string.speed));
long speedIntegerPart = (long) speedInUnit;
// Extract the decimal part
String speedFractionalPart = String.format("%.1f", (speedInUnit - speedIntegerPart)).substring(2);
appendDecimalUnit(builder, context.getResources().getQuantityString(speedId, getQuantityCount(speedInUnit), speedInUnit), speedIntegerPart, speedFractionalPart, isMetricUnits ? "kilometer per hour" : "mile per hour");
builder.append(".");
if (currentDistancePerTime != null) {
double currentDistancePerTimeInUnit = currentDistancePerTime.to(isMetricUnits);
if (currentDistancePerTimeInUnit > 0) {
builder.append(" ")
.append(context.getString(R.string.lap_speed));
long currentDistanceIntegerPart = (long) currentDistancePerTimeInUnit;
// Extract the decimal part
String currentDistanceFractionalPart = String.format("%.1f", (currentDistancePerTimeInUnit - currentDistanceIntegerPart)).substring(2);
appendDecimalUnit(builder, context.getResources().getQuantityString(speedId, getQuantityCount(currentDistancePerTimeInUnit), currentDistancePerTimeInUnit), currentDistanceIntegerPart, currentDistanceFractionalPart, isMetricUnits ? "kilometer per hour" : "mile per hour");
builder.append(".");
}
}
} else {
Duration time = distancePerTime.toPace(isMetricUnits);
int paceId = isMetricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
rate = context.getString(paceId, getAnnounceTime(context, time));
builder.append(" ")
.append(context.getString(R.string.pace));
appendDuration(context, builder, time);
builder.append(" ")
.append(context.getString(perUnitStringId))
.append(".");
Duration currentTime = currentDistancePerTime != null ? currentDistancePerTime.toPace(isMetricUnits) : Duration.ofMillis(0);
currentRate = context.getString(paceId, getAnnounceTime(context, currentTime));
currentRateMsg = context.getString(R.string.voice_pace_lap, currentRate);
if (!currentTime.isZero()) {
builder.append(" ")
.append(context.getString(R.string.lap_time));
appendDuration(context, builder, currentTime);
builder.append(" ")
.append(context.getString(perUnitStringId))
.append(".");
}
}
currentRateMsg = currentInterval == null ? "" : " " + currentRateMsg;
if (shouldVoiceAnnounceHeartRate()) {
if (sensorStatistics != null && sensorStatistics.hasHeartRate()) {
int averageHeartRate = Math.round(sensorStatistics.getAvgHeartRate().getBPM());
return context.getString(R.string.voice_template, totalDistance, getAnnounceTime(context, trackStatistics.getMovingTime()), rate) + currentRateMsg;
}
builder.append(" ")
.append(context.getString(R.string.average_heart_rate));
appendCardinal(builder, context.getString(R.string.sensor_state_heart_rate_value, averageHeartRate), averageHeartRate);
builder.append(".");
}
//TODO We might need to localize this using strings.xml if order is relevant.
private static String getAnnounceTime(Context context, Duration duration) {
String result = "";
if (currentInterval != null && currentInterval.hasAverageHeartRate()) {
int currentHeartRate = Math.round(currentInterval.getAverageHeartRate().getBPM());
int hours = (int) (duration.toHours());
int minutes = (int) (duration.toMinutes() % 60);
int seconds = (int) (duration.getSeconds() % 60);
if (hours != 0) {
String hoursText = context.getResources()
.getQuantityString(R.plurals.voiceHours, hours, hours);
result += hoursText + " ";
builder.append(" ")
.append(context.getString(R.string.current_heart_rate));
appendCardinal(builder, context.getString(R.string.sensor_state_heart_rate_value, currentHeartRate), currentHeartRate);
builder.append(".");
}
}
String minutesText = context.getResources()
.getQuantityString(R.plurals.voiceMinutes, minutes, minutes);
String secondsText = context.getResources()
.getQuantityString(R.plurals.voiceSeconds, seconds, seconds);
return result + minutesText + " " + secondsText;
return builder;
}
static int getQuantityCount(double d) {
return (int) d;
}
private static void appendDuration(@NonNull Context context, @NonNull SpannableStringBuilder builder, @NonNull Duration duration) {
int hours = (int) (duration.toHours());
int minutes = (int) (duration.toMinutes() % 60);
int seconds = (int) (duration.getSeconds() % 60);
if (hours > 0) {
appendDecimalUnit(builder, context.getResources().getQuantityString(R.plurals.voiceHours, hours, hours), hours, null, "hour");
}
if (minutes > 0) {
appendDecimalUnit(builder, context.getResources().getQuantityString(R.plurals.voiceMinutes, minutes, minutes), minutes, null, "minute");
}
if (seconds > 0 || duration.isZero()) {
appendDecimalUnit(builder, context.getResources().getQuantityString(R.plurals.voiceSeconds, seconds, seconds), seconds, null, "second");
}
}
/**
* Speaks as: 98.14 [UNIT] - ninety eight point one four [UNIT with correct plural form]
*/
private static void appendDecimalUnit(@NonNull SpannableStringBuilder builder, @NonNull String localizedText, long integerPart, @Nullable String fractionalPart, @NonNull String unit) {
TtsSpan.MeasureBuilder measureBuilder = new TtsSpan.MeasureBuilder()
.setUnit(unit);
if (fractionalPart == null) {
measureBuilder.setNumber(integerPart);
} else {
measureBuilder.setIntegerPart(integerPart)
.setFractionalPart(fractionalPart);
}
builder.append(" ")
.append(localizedText, measureBuilder.build(), SPAN_INCLUSIVE_EXCLUSIVE);
}
/**
* Speaks as: 98 - ninety eight
*/
private static void appendCardinal(@NonNull SpannableStringBuilder builder, @NonNull String localizedText, long number) {
builder.append(" ")
.append(localizedText, new TtsSpan.CardinalBuilder().setNumber(number).build(), SPAN_INCLUSIVE_EXCLUSIVE);
}
}
@@ -85,8 +85,15 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
Log.w(TAG, "Not started, should not be called.");
return null;
}
SensorDataSet sensorDataSet = remoteSensorManager.fill(trackPoint);
altitudeSumManager.fill(trackPoint);
SensorDataSet sensorDataSet = null;
BluetoothRemoteSensorManager localRemoteSensorManager = remoteSensorManager;
if (localRemoteSensorManager != null) {
sensorDataSet = localRemoteSensorManager.fill(trackPoint);
}
AltitudeSumManager localAltitudeSumManager = altitudeSumManager;
if (localAltitudeSumManager != null) {
localAltitudeSumManager.fill(trackPoint);
}
return sensorDataSet;
}
@@ -172,6 +172,13 @@ public class PreferencesUtils {
setString(keyId, resources.getString(valueId));
}
@VisibleForTesting
public static void setBoolean(int keyId, Boolean value) {
Editor editor = sharedPreferences.edit();
editor.putBoolean(getKey(keyId), value);
editor.apply();
}
static void setInt(int keyId, int value) {
Editor editor = sharedPreferences.edit();
editor.putInt(getKey(keyId), value);
@@ -316,6 +323,15 @@ public class PreferencesUtils {
return getFloat(R.string.voice_speed_rate_key, DEFAULT);
}
public static boolean shouldVoiceAnnounceHeartRate() {
return getBoolean(R.string.voice_announce_heart_rate_key, false);
}
@VisibleForTesting
public static void setVoiceAnnounceHeartRate(boolean value) {
setBoolean(R.string.voice_announce_heart_rate_key, value);
}
public static Distance getRecordingDistanceInterval() {
return Distance.of(getInt(R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefaultInternal()));
}
@@ -2,8 +2,6 @@ package de.dennisguse.opentracks.settings;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ScrollView;
import androidx.annotation.NonNull;
@@ -17,6 +15,7 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.stream.IntStream;
import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.ui.util.ArrayAdapterFilterDisabled;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutBinding;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
@@ -80,23 +79,16 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
itemTouchHelper.attachToRecyclerView(recyclerViewVisible);
// Spinner with items per row.
ArrayAdapter<Integer> spinnerAdapter = new ArrayAdapter<>(this,
ArrayAdapterFilterDisabled<Integer> rowsOptionAdapter = new ArrayAdapterFilterDisabled<>(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);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
viewBinding.rowsOptions.setAdapter(rowsOptionAdapter);
viewBinding.rowsOptions.setOnItemClickListener((parent, view, position, id) -> {
numColumns = position + 1;
gridLayoutManager.setSpanCount(numColumns);
});
viewBinding.spinnerOptions.setSelection(numColumns - 1);
viewBinding.rowsOptions.setText(rowsOptionAdapter.getItem(numColumns - 1).toString(), false);
// Recycler view with not visible stats.
layoutFieldsHidden = StatisticsUtils.filterVisible(layout, false);
@@ -25,6 +25,7 @@ import java.time.Instant;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
@@ -56,6 +57,8 @@ public class TrackStatistics {
private Speed maxSpeed;
private Float totalAltitudeGain_m = null;
private Float totalAltitudeLoss_m = null;
// The average heart rate seen on this track
private HeartRate avgHeartRate = null;
public TrackStatistics() {
reset();
@@ -76,6 +79,7 @@ public class TrackStatistics {
altitudeExtremities.set(other.altitudeExtremities.getMin(), other.altitudeExtremities.getMax());
totalAltitudeGain_m = other.totalAltitudeGain_m;
totalAltitudeLoss_m = other.totalAltitudeLoss_m;
avgHeartRate = other.avgHeartRate;
}
@VisibleForTesting
@@ -108,6 +112,19 @@ public class TrackStatistics {
stopTime = stopTime.isAfter(other.stopTime) ? stopTime : other.stopTime;
}
if (avgHeartRate == null) {
avgHeartRate = other.avgHeartRate;
} else {
if (other.avgHeartRate != null) {
// Using total time as weights for the averaging.
// Important to do this before total time is updated
avgHeartRate = HeartRate.of(
(totalTime.getSeconds() * avgHeartRate.getBPM() + other.totalTime.getSeconds() * other.avgHeartRate.getBPM())
/ (totalTime.getSeconds() + other.totalTime.getSeconds())
);
}
}
totalDistance = totalDistance.plus(other.totalDistance);
totalTime = totalTime.plus(other.totalTime);
movingTime = movingTime.plus(other.movingTime);
@@ -230,6 +247,15 @@ public class TrackStatistics {
return totalTime.minus(movingTime);
}
public boolean hasAverageHeartRate() {
return avgHeartRate != null;
}
@Nullable
public HeartRate getAverageHeartRate() {
return avgHeartRate;
}
/**
* Gets the average speed.
* This calculation only takes into account the displacement until the last point that was accounted for in statistics.
@@ -287,6 +313,12 @@ public class TrackStatistics {
}
}
public void setAverageHeartRate(HeartRate heartRate) {
if (heartRate != null) {
avgHeartRate = heartRate;
}
}
public boolean hasTotalAltitudeGain() {
return totalAltitudeGain_m != null;
}
@@ -26,6 +26,7 @@ import java.util.List;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
@@ -64,6 +65,8 @@ public class TrackStatisticsUpdater {
private final AltitudeRingBuffer altitudeBuffer;
private final SpeedRingBuffer speedBuffer;
private float averageHeartRateBPM;
private Duration totalHeartRateDuration = Duration.ZERO;
// The current segment's statistics
private final TrackStatistics currentSegment;
@@ -85,6 +88,7 @@ public class TrackStatisticsUpdater {
altitudeBuffer = new AltitudeRingBuffer(ALTITUDE_SMOOTHING_FACTOR);
speedBuffer = new SpeedRingBuffer(SPEED_SMOOTHING_FACTOR);
resetAverageHeartRate();
}
public TrackStatisticsUpdater(TrackStatisticsUpdater toCopy) {
@@ -95,6 +99,7 @@ public class TrackStatisticsUpdater {
this.speedBuffer = new SpeedRingBuffer(toCopy.speedBuffer);
this.lastTrackPoint = toCopy.lastTrackPoint;
resetAverageHeartRate();
}
public TrackStatistics getTrackStatistics() {
@@ -142,6 +147,17 @@ public class TrackStatisticsUpdater {
currentSegment.updateAltitudeExtremities(newAverage);
}
// Update heart rate
if (trackPoint.hasHeartRate() && lastTrackPoint != null) {
Duration trackPointDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
Duration newTotalDuration = totalHeartRateDuration.plus(trackPointDuration);
averageHeartRateBPM = (totalHeartRateDuration.toMillis() * averageHeartRateBPM + trackPointDuration.toMillis() * trackPoint.getHeartRate().getBPM()) / newTotalDuration.toMillis();
totalHeartRateDuration = newTotalDuration;
currentSegment.setAverageHeartRate(HeartRate.of(averageHeartRateBPM));
}
// Update total distance
if (trackPoint.hasSensorDistance()) {
// Sensor-based distance/speed
@@ -184,6 +200,12 @@ public class TrackStatisticsUpdater {
lastTrackPoint = null;
altitudeBuffer.reset();
speedBuffer.reset();
resetAverageHeartRate();
}
private void resetAverageHeartRate() {
averageHeartRateBPM = 0.0f;
totalHeartRateDuration = Duration.ZERO;
}
/**
@@ -5,11 +5,10 @@ import android.content.Context;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.DatePicker;
import android.widget.GridLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -17,6 +16,8 @@ import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.button.MaterialButtonToggleGroup;
import com.google.android.material.textfield.TextInputEditText;
import java.time.LocalDate;
@@ -61,17 +62,17 @@ public class FilterDialogFragment extends DialogFragment {
LayoutInflater inflater = LayoutInflater.from(getActivity());
View layout = inflater.inflate(R.layout.fragment_filter_dialog, null, false);
GridLayout itemsLayout = layout.findViewById(R.id.filter_items);
MaterialButtonToggleGroup itemsLayout = layout.findViewById(R.id.filter_items);
builder.setView(layout);
for (FilterItem item : filterItems) {
View view = inflater.inflate(R.layout.fragment_filter_dialog_item, null);
CheckBox checkBox = view.findViewById(R.id.filter_dialog_check_button);
checkBox.setText(item.value);
checkBox.setChecked(item.isChecked);
checkBox.setTag(item.id);
checkBox.setOnClickListener(v -> item.isChecked = !item.isChecked);
MaterialButton button = view.findViewById(R.id.filter_dialog_check_button);
button.setText(item.value);
button.setChecked(item.isChecked);
button.setTag(item.id);
button.setOnClickListener(v -> item.isChecked = !item.isChecked);
itemsLayout.addView(view);
}
@@ -43,8 +43,8 @@ public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<Recycl
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.title.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader) : R.style.TextAppearance_OpenTracks_HiddenHeader);
viewHolder.value.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue) : R.style.TextAppearance_OpenTracks_HiddenValue);
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);
@@ -8,6 +8,7 @@ import java.util.List;
import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -102,6 +103,7 @@ public class IntervalStatistics {
private Duration time = Duration.ofSeconds(0);
private Float gain_m;
private Float loss_m;
private HeartRate avgHeartRate;
public Interval() {
}
@@ -117,6 +119,7 @@ public class IntervalStatistics {
time = i.time;
gain_m = i.gain_m;
loss_m = i.loss_m;
avgHeartRate = i.avgHeartRate;
}
public Interval(Interval i) {
@@ -124,6 +127,7 @@ public class IntervalStatistics {
time = i.time;
gain_m = i.gain_m;
loss_m = i.loss_m;
avgHeartRate = i.avgHeartRate;
}
private void adjust(double adjustFactor) {
@@ -155,11 +159,20 @@ public class IntervalStatistics {
return loss_m;
}
public boolean hasAverageHeartRate() {
return avgHeartRate != null;
}
public HeartRate getAverageHeartRate() {
return avgHeartRate;
}
private void add(TrackStatistics trackStatistics, @Nullable TrackPoint lastTrackPoint) {
distance = distance.plus(trackStatistics.getTotalDistance());
time = time.plus(trackStatistics.getTotalTime());
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
loss_m = trackStatistics.hasTotalAltitudeLoss() ? trackStatistics.getTotalAltitudeLoss() : loss_m;
avgHeartRate = trackStatistics.getAverageHeartRate();
if (lastTrackPoint == null) {
return;
}
@@ -176,6 +189,7 @@ public class IntervalStatistics {
time = trackStatistics.getTotalTime();
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
loss_m = trackStatistics.hasTotalAltitudeLoss() ? trackStatistics.getTotalAltitudeLoss() : loss_m;
avgHeartRate = trackStatistics.getAverageHeartRate();
}
}
}
@@ -5,7 +5,6 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.TextView;
@@ -33,6 +32,7 @@ public class IntervalsFragment extends Fragment {
private static final String FROM_TOP_TO_BOTTOM_KEY = "fromTopToBottom";
private static final String TRACK_ID_KEY = "trackId";
private static final String SELECTED_INTERVAL_KEY = "selectedIntervalKey";
private IntervalStatisticsModel viewModel;
protected IntervalStatisticsAdapter.StackMode stackModeListView;
@@ -41,7 +41,7 @@ public class IntervalsFragment extends Fragment {
private Track.Id trackId;
private boolean metricUnits;
private IntervalStatisticsAdapter adapter;
private ArrayAdapter<IntervalStatisticsModel.IntervalOption> spinnerAdapter;
private ArrayAdapter<IntervalStatisticsModel.IntervalOption> intervalsAdapter;
private boolean isReportSpeed;
@@ -50,8 +50,8 @@ public class IntervalsFragment extends Fragment {
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
if (PreferencesUtils.isKey(R.string.stats_units_key, key) || PreferencesUtils.isKey(R.string.stats_rate_key, key)) {
updateIntervals(PreferencesUtils.isMetricUnits(), selectedInterval);
if (spinnerAdapter != null) {
spinnerAdapter.notifyDataSetChanged();
if (intervalsAdapter != null) {
intervalsAdapter.notifyDataSetChanged();
}
}
};
@@ -76,6 +76,15 @@ public class IntervalsFragment extends Fragment {
super.onCreate(savedInstanceState);
stackModeListView = getArguments().getBoolean(FROM_TOP_TO_BOTTOM_KEY, true) ? IntervalStatisticsAdapter.StackMode.STACK_FROM_TOP : IntervalStatisticsAdapter.StackMode.STACK_FROM_BOTTOM;
trackId = getArguments().getParcelable(TRACK_ID_KEY);
if (savedInstanceState != null) {
selectedInterval = (IntervalStatisticsModel.IntervalOption) savedInstanceState.getSerializable(SELECTED_INTERVAL_KEY);
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(SELECTED_INTERVAL_KEY, selectedInterval);
}
@Override
@@ -93,7 +102,7 @@ public class IntervalsFragment extends Fragment {
// TODO handle empty view: before we did viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
viewBinding.intervalList.setAdapter(adapter);
spinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, IntervalStatisticsModel.IntervalOption.values()) {
intervalsAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, IntervalStatisticsModel.IntervalOption.values()) {
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
@@ -112,17 +121,13 @@ public class IntervalsFragment extends Fragment {
}
};
viewBinding.spinnerIntervals.setAdapter(spinnerAdapter);
viewBinding.spinnerIntervals.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
updateIntervals(metricUnits, IntervalStatisticsModel.IntervalOption.values()[i]);
}
viewBinding.intervalsDropdown.setAdapter(intervalsAdapter);
viewBinding.intervalsDropdown.setOnItemClickListener((parent, view1, position, id) -> updateIntervals(metricUnits, IntervalStatisticsModel.IntervalOption.values()[position]));
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
viewBinding.intervalsDropdown.setText(
getContext().getString(R.string.value_integer_kilometer, Integer.parseInt(selectedInterval != null ? selectedInterval.toString() : IntervalStatisticsModel.IntervalOption.values()[0].toString())),
false
);
}
@Override
@@ -173,6 +178,7 @@ public class IntervalsFragment extends Fragment {
if (viewModel == null) {
return;
}
viewBinding.intervalRate.setText(isReportSpeed ? getString(R.string.stats_speed) : getString(R.string.stats_pace));
LiveData<List<IntervalStatistics.Interval>> liveData = viewModel.getIntervalStats(trackId, metricUnits, selectedInterval);
liveData.observe(getActivity(), intervalList -> adapter.swapData(intervalList, metricUnits, isReportSpeed));
@@ -0,0 +1,35 @@
package de.dennisguse.opentracks.ui.util;
import android.content.Context;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import androidx.annotation.NonNull;
// https://github.com/material-components/material-components-android/issues/1464
public class ArrayAdapterFilterDisabled<T> extends ArrayAdapter<T> {
public ArrayAdapterFilterDisabled(@NonNull Context context, int resource, @NonNull T[] objects) {
super(context, resource, objects);
}
@NonNull
@Override
public Filter getFilter() {
return new NeverFilter();
}
private class NeverFilter extends Filter {
protected FilterResults performFiltering(CharSequence prefix) {
return new FilterResults();
}
protected void publishResults(CharSequence constraint, FilterResults results) {
if (ArrayAdapterFilterDisabled.this.getCount() > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
}
}
@@ -0,0 +1,22 @@
package de.dennisguse.opentracks.ui.util;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
// https://github.com/material-components/material-components-android/issues/1464
public class AutoCompleteTextViewForDropdown extends MaterialAutoCompleteTextView {
public AutoCompleteTextViewForDropdown(@NonNull final Context context, @Nullable final AttributeSet attributeSet) {
super(context, attributeSet);
}
@Override
public boolean getFreezesText() {
return false;
}
}
@@ -17,11 +17,9 @@
package de.dennisguse.opentracks.ui.util;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.time.OffsetDateTime;
@@ -77,11 +75,9 @@ public class ListItemUtils {
TextView timeDistanceTextView = view.findViewById(R.id.list_item_time_distance);
String timeDistanceText;
if (isRecording) {
timeDistanceTextView.setTextColor(context.getResources().getColor(isPaused ? android.R.color.white : R.color.recording_text));
timeDistanceText = context.getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
} else {
// Match list_item_time_distance in list_item.xml
timeDistanceTextView.setTextAppearance(context, R.style.TextSmall);
timeDistanceText = getTimeDistance(totalTime, totalDistance);
if (markerCount > 0) {
timeDistanceText += " \u2027";
@@ -111,7 +107,7 @@ public class ListItemUtils {
String timeValue = null;
if (!isRecording) {
dateValue = StringUtils.formatDateTodayRelative(context, offsetDateTime);
timeValue = offsetDateTime.format(DateTimeFormatter.ofPattern("hh:mm"));
timeValue = offsetDateTime.format(DateTimeFormatter.ofPattern("HH:mm"));
}
setTextView(context, dateTextView, dateValue, hasPhoto);
setTextView(context, timeTextView, timeValue, hasPhoto);
@@ -132,10 +128,6 @@ public class ListItemUtils {
timeDistanceTextView.setSingleLine(true);
setTextView(context, categoryDescriptionTextView, categoryDescription, hasPhoto);
}
// Adjust iconImageView layout gravity
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) iconImageView.getLayoutParams();
params.gravity = timeDistanceTextView.getVisibility() == View.GONE && markerCountIcon.getVisibility() == View.GONE ? Gravity.TOP : Gravity.CENTER_VERTICAL;
}
/**
@@ -42,14 +42,14 @@ public class ThemeUtils {
}
public static int getFontSizeSmallInPx(Context context) {
TypedArray typedArray = context.obtainStyledAttributes(R.style.TextSmall, new int[]{android.R.attr.textSize});
TypedArray typedArray = context.obtainStyledAttributes(R.style.TextAppearance_MaterialComponents_Body2, new int[]{android.R.attr.textSize});
int fontSize = typedArray.getDimensionPixelSize(0, 12);
typedArray.recycle();
return fontSize;
}
public static int getFontSizeMediumInPx(Context context) {
TypedArray typedArray = context.obtainStyledAttributes(R.style.TextMedium, new int[]{android.R.attr.textSize});
TypedArray typedArray = context.obtainStyledAttributes(R.style.TextAppearance_MaterialComponents_Body1, new int[]{android.R.attr.textSize});
int fontSize = typedArray.getDimensionPixelSize(0, 15);
typedArray.recycle();
return fontSize;
@@ -77,7 +77,7 @@ public class IntentUtils {
public static void persistDirectoryAccessPermission(Context context, Uri directoryUri, int existingFlags) {
int newFlags = existingFlags & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
context.getContentResolver().takePersistableUriPermission(directoryUri, newFlags);
context.getApplicationContext().getContentResolver().takePersistableUriPermission(directoryUri, newFlags);
}
public static void releaseDirectoryAccessPermission(Context context, final Uri documentUri) {
@@ -85,7 +85,7 @@ public class IntentUtils {
return;
}
context.getContentResolver().getPersistedUriPermissions().stream()
context.getApplicationContext().getContentResolver().getPersistedUriPermissions().stream()
.map(UriPermission::getUri)
.filter(documentUri::equals)
.forEach(u -> context.getContentResolver().releasePersistableUriPermission(u, 0));
@@ -96,7 +96,7 @@ public class IntentUtils {
return null;
}
try {
return DocumentFile.fromTreeUri(context, directoryUri);
return DocumentFile.fromTreeUri(context.getApplicationContext(), directoryUri);
} catch (Exception e) {
Log.w(TAG, "Could not decode directory: " + e.getMessage());
}
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/colorAccentTransparent" android:state_activated="true" />
<item android:drawable="@color/opentracks_secondary_transparent" android:state_activated="true" />
<item android:drawable="@android:color/transparent" />
</selector>
@@ -38,17 +38,17 @@ limitations under the License.
<TextView
android:id="@+id/controller_total_time"
style="@style/TextSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle2"
android:layout_centerInParent="true"
android:textColor="@android:color/white" />
<TextView
android:id="@+id/controller_status"
style="@style/TextMicro"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle2"
android:layout_above="@id/controller_total_time"
android:layout_centerHorizontal="true"
android:singleLine="true" />
+1 -1
View File
@@ -89,7 +89,7 @@ limitations under the License.
android:layout_weight="24">
<TextView
style="@style/TextLarge"
style="?attr/textAppearanceHeadline6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/track_list_empty_message" />
+12 -8
View File
@@ -32,54 +32,55 @@
<TextView
android:id="@+id/about_text_description"
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAppearance="?attr/textAppearanceSubtitle1"
android:gravity="center"
android:padding="12dp" />
</LinearLayout>
<View style="@style/StatsHorizontalLine" />
<View style="@style/HorizontalLine" />
<TextView
android:id="@+id/about_text_version_name"
style="@style/TextSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceSubtitle1"
android:gravity="center"
android:padding="6dp" />
<TextView
android:id="@+id/about_text_version_code"
style="@style/TextSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBody1"
android:gravity="center"
android:padding="6dp" />
<TextView
android:id="@+id/about_app_url"
style="@style/TextSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBody1"
android:autoLink="web"
android:gravity="center"
android:padding="6dp" />
<TextView
style="@style/TextSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBody1"
android:gravity="center"
android:linksClickable="true"
android:padding="6dp"
android:text="@string/about_history" />
<View style="@style/StatsHorizontalLine" />
<View style="@style/HorizontalLine" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBody1"
android:gravity="center"
android:padding="12dp"
android:text="@string/about_third_party_data" />
@@ -87,15 +88,17 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBody1"
android:gravity="center"
android:padding="12dp"
android:text="@string/third_party_data" />
<View style="@style/StatsHorizontalLine" />
<View style="@style/HorizontalLine" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceSubtitle1"
android:gravity="center"
android:padding="12dp"
android:text="@string/about_libraries" />
@@ -103,6 +106,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBody1"
android:gravity="center"
android:linksClickable="true"
android:text="@string/third_party_libraries" />
@@ -7,27 +7,20 @@
<include layout="@layout/toolbar" />
<LinearLayout
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/rows_options_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:orientation="horizontal">
android:hint="@string/settings_recording_customize_layout_select_columns">
<TextView
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
<de.dennisguse.opentracks.ui.util.AutoCompleteTextViewForDropdown
android:id="@+id/rows_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/settings_recording_customize_layout_select_columns" />
android:inputType="none" />
<Spinner
android:id="@+id/spinner_options"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center" />
</LinearLayout>
</com.google.android.material.textfield.TextInputLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"
@@ -22,6 +22,7 @@
android:id="@+id/custom_layout_list_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:errorEnabled="true"
android:layout_margin="16dp">
@@ -39,17 +40,22 @@
android:id="@+id/custom_layout_list_bottom_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:gravity="center_vertical"
android:layout_margin="16dp"
android:gravity="end"
android:orientation="horizontal">
<Button
<com.google.android.material.button.MaterialButton
android:id="@+id/custom_layout_list_cancel_button"
style="@style/BottomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:text="@android:string/cancel" />
<Button
<com.google.android.material.button.MaterialButton
android:id="@+id/custom_layout_list_ok_button"
style="@style/BottomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:enabled="false"
android:text="@android:string/ok" />
</LinearLayout>
+1 -1
View File
@@ -13,7 +13,7 @@
<TextView
android:id="@+id/aggregated_stats_empty_view"
style="@style/TextLarge"
style="?attr/textAppearanceHeadline4"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
@@ -4,6 +4,9 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:paddingBottom="24dp">
<!-- Guidelines -->
@@ -33,8 +36,7 @@
android:id="@+id/aggregated_stats_sport_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toStartOf="@id/aggregated_stats_type_label"
app:layout_constraintStart_toStartOf="@id/guideline1"
app:layout_constraintTop_toTopOf="parent"
app:tint="?android:textColorPrimary"
@@ -42,10 +44,11 @@
<TextView
android:id="@+id/aggregated_stats_type_label"
style="@style/LargeLabel"
android:layout_marginStart="16dp"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:layout_marginBottom="0dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_sport_icon"
app:layout_constraintStart_toEndOf="@+id/aggregated_stats_sport_icon"
app:layout_constraintTop_toTopOf="@+id/aggregated_stats_sport_icon"
@@ -53,10 +56,11 @@
<TextView
android:id="@+id/aggregated_stats_num_tracks"
style="@style/LargeLabel"
android:layout_marginStart="4dp"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:layout_marginBottom="0dp"
android:layout_marginStart="4dp"
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_type_label"
app:layout_constraintStart_toEndOf="@+id/aggregated_stats_type_label"
app:layout_constraintTop_toTopOf="@+id/aggregated_stats_type_label"
@@ -65,8 +69,9 @@
<!-- Distance -->
<TextView
android:id="@+id/aggregated_stats_distance"
style="@style/StatsPrimaryValue"
android:layout_marginTop="16dp"
style="@style/TextAppearance.OpenTracks.PrimaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@id/aggregated_stats_distance_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline1"
@@ -76,8 +81,9 @@
<TextView
android:id="@+id/aggregated_stats_distance_unit"
style="@style/StatsUnit"
android:singleLine="true"
style="@style/TextAppearance.OpenTracks.PrimaryUnit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_distance"
app:layout_constraintEnd_toEndOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@+id/aggregated_stats_distance"
@@ -86,9 +92,9 @@
<!-- Time -->
<TextView
android:id="@+id/aggregated_stats_time"
style="@style/StatsPrimaryValue"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
style="@style/TextAppearance.OpenTracks.PrimaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="@id/guideline3"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toBottomOf="@+id/aggregated_stats_sport_icon"
@@ -97,8 +103,9 @@
<!-- Avg. rate -->
<TextView
android:id="@+id/aggregated_stats_avg_rate"
style="@style/StatsSmallValue"
android:layout_marginTop="24dp"
style="@style/TextAppearance.OpenTracks.SecondaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@+id/aggregated_stats_avg_rate_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline1"
@@ -107,7 +114,9 @@
<TextView
android:id="@+id/aggregated_stats_avg_rate_unit"
style="@style/StatsUnit"
style="@style/TextAppearance.OpenTracks.PrimaryUnit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_avg_rate"
app:layout_constraintEnd_toEndOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@+id/aggregated_stats_avg_rate"
@@ -115,8 +124,9 @@
<TextView
android:id="@+id/aggregated_stats_avg_rate_label"
style="@style/StatsTinyLabel"
android:singleLine="true"
style="@style/TextAppearance.OpenTracks.SecondaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline1"
@@ -126,8 +136,9 @@
<!-- Max. rate -->
<TextView
android:id="@+id/aggregated_stats_max_rate"
style="@style/StatsSmallValue"
android:layout_marginTop="24dp"
style="@style/TextAppearance.OpenTracks.SecondaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@+id/aggregated_stats_max_rate_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
@@ -136,7 +147,9 @@
<TextView
android:id="@+id/aggregated_stats_max_rate_unit"
style="@style/StatsUnit"
style="@style/TextAppearance.OpenTracks.PrimaryUnit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_max_rate"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@+id/aggregated_stats_max_rate"
@@ -144,8 +157,9 @@
<TextView
android:id="@+id/aggregated_stats_max_rate_label"
style="@style/StatsTinyLabel"
android:singleLine="true"
style="@style/TextAppearance.OpenTracks.SecondaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
+2 -2
View File
@@ -39,9 +39,9 @@ limitations under the License.
<TextView
android:id="@+id/controller_total_time"
style="@style/TextSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle2"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:gravity="center"
@@ -50,9 +50,9 @@ limitations under the License.
<TextView
android:id="@+id/controller_status"
style="@style/TextMicro"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle2"
android:layout_above="@id/controller_total_time"
android:layout_alignParentStart="true"
android:gravity="center"
+2 -2
View File
@@ -3,7 +3,7 @@
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">
style="@style/OpenTracksCardView">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
@@ -16,7 +16,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
tools:text="Profile/Custom Layout Title"
style="@style/LargeLabel"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
android:layout_margin="8dp"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
+1 -1
View File
@@ -3,7 +3,7 @@
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">
style="@style/OpenTracksCardView">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
+38 -14
View File
@@ -28,9 +28,9 @@
<TextView
android:id="@+id/export_progress_done"
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:layout_marginStart="24dp"
android:layout_marginTop="48dp"
app:layout_constraintEnd_toStartOf="@id/export_progress_slash"
@@ -41,9 +41,9 @@
<TextView
android:id="@+id/export_progress_slash"
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:text="@string/generic_separator_done_total"
app:layout_constraintBottom_toBottomOf="@id/export_progress_done"
app:layout_constraintEnd_toStartOf="@id/export_progress_total"
@@ -52,9 +52,9 @@
<TextView
android:id="@+id/export_progress_total"
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:layout_marginEnd="24dp"
app:layout_constraintBottom_toBottomOf="@id/export_progress_slash"
app:layout_constraintEnd_toEndOf="parent"
@@ -62,9 +62,8 @@
app:layout_constraintTop_toTopOf="@id/export_progress_slash"
tools:text="10" />
<ProgressBar
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/export_progress_bar"
style="@style/ProgressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
@@ -79,7 +78,11 @@
<!-- Tracks exported (new tracks) -->
<TextView
android:id="@+id/export_progress_summary_new_msg"
style="@style/TextImportExportSummaryStart"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:gravity="end"
android:layout_margin="16dp"
android:text="@string/export_progress_summary_new_msg"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical_half"
app:layout_constraintStart_toStartOf="parent"
@@ -87,7 +90,9 @@
<TextView
android:id="@+id/export_progress_summary_new"
style="@style/TextImportExportSummaryEnd"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
app:layout_constraintBottom_toBottomOf="@id/export_progress_summary_new_msg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical_half"
@@ -112,7 +117,11 @@
<!-- Tracks overwritten -->
<TextView
android:id="@+id/export_progress_summary_overwrite_msg"
style="@style/TextImportExportSummaryStart"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:gravity="end"
android:layout_margin="16dp"
android:text="@string/export_progress_summary_overwrite_msg"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical_half"
app:layout_constraintStart_toStartOf="parent"
@@ -120,7 +129,9 @@
<TextView
android:id="@+id/export_progress_summary_overwrite"
style="@style/TextImportExportSummaryEnd"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
app:layout_constraintBottom_toBottomOf="@id/export_progress_summary_overwrite_msg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical_half"
@@ -145,7 +156,11 @@
<!-- Tracks skipped -->
<TextView
android:id="@+id/export_progress_summary_skip_msg"
style="@style/TextImportExportSummaryStart"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:gravity="end"
android:layout_margin="16dp"
android:text="@string/export_progress_summary_skip_msg"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical_half"
app:layout_constraintStart_toStartOf="parent"
@@ -153,7 +168,9 @@
<TextView
android:id="@+id/export_progress_summary_skip"
style="@style/TextImportExportSummaryEnd"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
app:layout_constraintBottom_toBottomOf="@id/export_progress_summary_skip_msg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical_half"
@@ -178,7 +195,11 @@
<!-- Tracks errors -->
<TextView
android:id="@+id/export_progress_summary_errors_msg"
style="@style/TextImportExportSummaryStart"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:gravity="end"
android:layout_margin="16dp"
android:text="@string/export_progress_summary_errors_msg"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical_half"
app:layout_constraintStart_toStartOf="parent"
@@ -186,7 +207,9 @@
<TextView
android:id="@+id/export_progress_summary_errors"
style="@style/TextImportExportSummaryEnd"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
app:layout_constraintBottom_toBottomOf="@id/export_progress_summary_errors_msg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical_half"
@@ -224,9 +247,9 @@
<TextView
android:id="@+id/export_progress_alert_msg"
style="@style/TextMediumPrimary"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:layout_marginTop="48dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
@@ -276,6 +299,7 @@
android:text="@string/generic_overwrite"
android:visibility="gone"
tools:visibility="visible"/>
</LinearLayout>
</LinearLayout>
+88 -88
View File
@@ -1,106 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView 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"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
xmlns:tools="http://schemas.android.com/tools">
<ScrollView style="@style/ScrollView.WithBottomButtons">
<androidx.constraintlayout.widget.ConstraintLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/filter_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
android:padding="8dp"
android:orientation="vertical"
app:singleSelection="false"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/filter_date_from" />
<GridLayout
android:id="@+id/filter_items"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/filter_date_from"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generic_from"
android:imeOptions="actionNext"
android:importantForAutofill="no"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="@id/filter_items"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/filter_date_picker_from">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/filter_date_edit_text_from"
android:drawableStart="@drawable/ic_baseline_date_range_24"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="2"
android:orientation="horizontal"
android:padding="8dp"
android:alignmentMode="alignBounds"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/filter_date_from" />
android:enabled="true"
android:focusable="false"
android:focusableInTouchMode="false"
android:inputType="date"
android:selectAllOnFocus="true" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/filter_date_from"
</com.google.android.material.textfield.TextInputLayout>
<DatePicker
android:id="@+id/filter_date_picker_from"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
android:paddingLeft="8dp"
android:paddingRight="8dp"
app:layout_constraintTop_toBottomOf="@id/filter_date_from"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/filter_date_to" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/filter_date_to"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generic_to"
android:imeOptions="actionNext"
android:importantForAutofill="no"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="@id/filter_date_picker_from"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/filter_date_picker_to">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/filter_date_edit_text_to"
android:drawableStart="@drawable/ic_baseline_date_range_24"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generic_from"
android:imeOptions="actionNext"
android:importantForAutofill="no"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="@id/filter_items"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/filter_date_picker_from">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/filter_date_edit_text_from"
android:drawableStart="@drawable/ic_baseline_date_range_24"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="true"
android:focusable="false"
android:focusableInTouchMode="false"
android:inputType="date"
android:selectAllOnFocus="true" />
</com.google.android.material.textfield.TextInputLayout>
android:enabled="true"
android:focusable="false"
android:focusableInTouchMode="false"
android:inputType="date"
android:selectAllOnFocus="true" />
<DatePicker
android:id="@+id/filter_date_picker_from"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
android:paddingLeft="8dp"
android:paddingRight="8dp"
app:layout_constraintTop_toBottomOf="@id/filter_date_from"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/filter_date_to" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/filter_date_to"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generic_to"
android:imeOptions="actionNext"
android:importantForAutofill="no"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="@id/filter_date_picker_from"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/filter_date_picker_to">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/filter_date_edit_text_to"
android:drawableStart="@drawable/ic_baseline_date_range_24"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="true"
android:focusable="false"
android:focusableInTouchMode="false"
android:inputType="date"
android:selectAllOnFocus="true" />
</com.google.android.material.textfield.TextInputLayout>
<DatePicker
android:id="@+id/filter_date_picker_to"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
android:paddingLeft="8dp"
android:paddingRight="8dp"
app:layout_constraintTop_toBottomOf="@id/filter_date_to"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<DatePicker
android:id="@+id/filter_date_picker_to"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
android:paddingLeft="8dp"
android:paddingRight="8dp"
app:layout_constraintTop_toBottomOf="@id/filter_date_to"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</LinearLayout>
</ScrollView>
@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/filter_dialog_check_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:checkable="true"
android:gravity="center"
tools:text="Check Button" />
+27 -36
View File
@@ -17,129 +17,120 @@
android:padding="20dp">
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/help_feature_title"
android:textStyle="bold" />
style="?attr/textAppearanceHeadline6"
android:text="@string/help_feature_title" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceBody1"
android:text="@string/help_feature_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceHeadline6"
android:layout_marginTop="20dp"
android:text="@string/help_track_map_title"
android:textStyle="bold" />
android:text="@string/help_track_map_title" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceBody1"
android:linksClickable="true"
android:text="@string/help_track_map_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceHeadline6"
android:layout_marginTop="20dp"
android:text="@string/help_recording_title"
android:textStyle="bold" />
android:text="@string/help_recording_title" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceBody1"
android:text="@string/help_recording_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceHeadline6"
android:layout_marginTop="20dp"
android:text="@string/help_storage_pictures_title"
android:textStyle="bold" />
android:text="@string/help_storage_pictures_title" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceBody1"
android:text="@string/help_storage_pictures_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceHeadline6"
android:layout_marginTop="20dp"
android:text="@string/help_track_export_title"
android:textStyle="bold" />
android:text="@string/help_track_export_title" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceBody1"
android:text="@string/help_track_export_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/help_data_google_play_services_title"
android:textStyle="bold" />
style="?attr/textAppearanceHeadline6"
android:text="@string/help_data_google_play_services_title" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceBody1"
android:text="@android:string/no" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceHeadline6"
android:layout_marginTop="20dp"
android:text="@string/help_data_storage_title"
android:textStyle="bold" />
android:text="@string/help_data_storage_title" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceBody1"
android:text="@string/help_data_storage_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceHeadline6"
android:layout_marginTop="20dp"
android:text="@string/help_gps_troubleshooting_title"
android:textStyle="bold" />
android:text="@string/help_gps_troubleshooting_title" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceBody1"
android:linksClickable="true"
android:text="@string/help_gps_troubleshooting_content" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceHeadline6"
android:layout_marginTop="20dp"
android:text="@string/help_support_title"
android:textStyle="bold" />
android:text="@string/help_support_title" />
<TextView
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textAppearanceBody1"
android:linksClickable="true"
android:text="@string/help_support_content" />
</LinearLayout>
+29 -12
View File
@@ -28,9 +28,9 @@
<TextView
android:id="@+id/import_progress_done"
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:layout_marginStart="24dp"
android:layout_marginTop="48dp"
app:layout_constraintEnd_toStartOf="@id/import_progress_slash"
@@ -41,9 +41,9 @@
<TextView
android:id="@+id/import_progress_slash"
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:text="@string/generic_separator_done_total"
app:layout_constraintBottom_toBottomOf="@id/import_progress_done"
app:layout_constraintEnd_toStartOf="@id/import_progress_total"
@@ -52,9 +52,9 @@
<TextView
android:id="@+id/import_progress_total"
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:layout_marginEnd="24dp"
app:layout_constraintBottom_toBottomOf="@id/import_progress_slash"
app:layout_constraintEnd_toEndOf="parent"
@@ -62,9 +62,8 @@
app:layout_constraintTop_toTopOf="@id/import_progress_slash"
tools:text="10" />
<ProgressBar
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/import_progress_bar"
style="@style/ProgressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
@@ -79,7 +78,11 @@
<!-- Files imported correctly -->
<TextView
android:id="@+id/import_progress_summary_ok_msg"
style="@style/TextImportExportSummaryStart"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:gravity="end"
android:layout_margin="16dp"
android:text="@string/import_progress_summary_ok_msg"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical_half"
app:layout_constraintStart_toStartOf="parent"
@@ -87,7 +90,9 @@
<TextView
android:id="@+id/import_progress_summary_ok"
style="@style/TextImportExportSummaryEnd"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
app:layout_constraintBottom_toBottomOf="@id/import_progress_summary_ok_msg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical_half"
@@ -112,7 +117,11 @@
<!-- Already exists files -->
<TextView
android:id="@+id/import_progress_summary_exists_msg"
style="@style/TextImportExportSummaryStart"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:gravity="end"
android:layout_margin="16dp"
android:text="@string/import_progress_summary_exists_msg"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical_half"
app:layout_constraintStart_toStartOf="parent"
@@ -120,7 +129,9 @@
<TextView
android:id="@+id/import_progress_summary_exists"
style="@style/TextImportExportSummaryEnd"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
app:layout_constraintBottom_toBottomOf="@id/import_progress_summary_exists_msg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical_half"
@@ -145,7 +156,11 @@
<!-- Importing error files -->
<TextView
android:id="@+id/import_progress_summary_errors_msg"
style="@style/TextImportExportSummaryStart"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:gravity="end"
android:layout_margin="16dp"
android:text="@string/import_progress_summary_errors_msg"
app:layout_constraintEnd_toEndOf="@id/guideline_vertical_half"
app:layout_constraintStart_toStartOf="parent"
@@ -153,7 +168,9 @@
<TextView
android:id="@+id/import_progress_summary_errors"
style="@style/TextImportExportSummaryEnd"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
app:layout_constraintBottom_toBottomOf="@id/import_progress_summary_errors_msg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical_half"
@@ -191,9 +208,9 @@
<TextView
android:id="@+id/import_progress_alert_msg"
style="@style/TextMediumPrimary"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceSubtitle1"
android:layout_marginTop="48dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
+24 -21
View File
@@ -3,49 +3,52 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:orientation="vertical">
<LinearLayout
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/intervals_input_layout"
style="?attr/textInputOutlinedExposedDropdownMenuStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:orientation="horizontal">
android:layout_marginBottom="8dp"
android:hint="@string/stats_split_interval">
<TextView
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
<de.dennisguse.opentracks.ui.util.AutoCompleteTextViewForDropdown
android:id="@+id/intervals_dropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/stats_split_interval" />
android:inputType="none" />
<Spinner
android:id="@+id/spinner_intervals"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center" />
</LinearLayout>
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:orientation="horizontal">
<TextView
android:id="@+id/interval_distance"
style="@style/IntervalLabel"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
android:text="@string/stats_distance" />
<TextView
android:id="@+id/interval_rate"
style="@style/IntervalLabel"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
tools:text="Speed" />
<TextView
android:id="@+id/interval_altitude"
style="@style/IntervalLabel"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
android:text="@string/stats_altitude" />
</LinearLayout>
@@ -65,9 +68,9 @@
<TextView
android:id="@+id/interval_list_empty_view"
style="@style/TextMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?attr/textAppearanceHeadline4"
android:gravity="center_horizontal|center_vertical"
android:text="@string/interval_list_empty_message" />
@@ -10,18 +10,20 @@
<TextView
android:id="@+id/interval_item_distance"
style="@style/TextMediumPrimary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
style="?attr/textAppearanceBody1"
tools:text="1 km" />
<TextView
android:id="@+id/interval_item_rate"
style="@style/TextMediumPrimary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
style="?attr/textAppearanceBody1"
tools:text="15 km/h" />
<LinearLayout
@@ -32,17 +34,19 @@
<TextView
android:id="@+id/interval_item_gain"
style="@style/TextSmall"
android:layout_width="match_parent"
app:drawableStartCompat="@drawable/ic_arrow_drop_up_24"
tools:text="100 m" />
android:layout_height="wrap_content"
style="?attr/textAppearanceBody2"
tools:text="100 m"
app:drawableStartCompat="@drawable/ic_arrow_drop_up_24" />
<TextView
android:id="@+id/interval_item_loss"
style="@style/TextSmall"
android:layout_width="match_parent"
app:drawableStartCompat="@drawable/ic_arrow_drop_down_24"
tools:text="100 m" />
android:layout_height="wrap_content"
style="?attr/textAppearanceBody2"
tools:text="100 m"
app:drawableStartCompat="@drawable/ic_arrow_drop_down_24" />
</LinearLayout>
+132 -138
View File
@@ -1,152 +1,146 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2012 Google Inc.
<?xml version="1.0" encoding="utf-8"?>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@drawable/activated_background"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="12dp"
android:paddingBottom="12dp">
<!-- Photo -->
<ImageView
android:id="@+id/list_item_photo"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="4dp"
android:contentDescription="@string/image_photo_marker"
android:scaleType="centerCrop" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- A gradient on top of the text -->
<ImageView
android:id="@+id/list_item_text_gradient"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha=".6"
android:visibility="gone"
tools:visibility="visible"
android:contentDescription="@string/image_photo_marker"
android:src="@drawable/text_gradient" />
<!-- Table 1 -->
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingTop="8dp"
android:paddingStart="4dp"
android:paddingEnd="12dp"
android:paddingBottom="12dp"
android:shrinkColumns="1"
android:stretchColumns="1">
<!-- Table 1: Row 1 -->
<TableRow>
<!-- Table 1: Row 1: Column 1: (icon) -->
<ImageView
android:id="@+id/list_item_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="4dp"
android:layout_marginEnd="8dp"
android:contentDescription="@string/image_track"
app:tint="?android:textColorPrimary" />
<!-- Table 1: Row 1: Column 2: (Table 2) -->
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:shrinkColumns="0"
android:stretchColumns="0">
<TableRow>
<!-- Table 2: Row 1: Column 1: (name) -->
<TextView
android:id="@+id/list_item_name"
style="@style/TextMediumPrimary"
android:layout_width="match_parent"
android:layout_gravity="bottom"
android:ellipsize="end"
android:singleLine="true"
tools:text="Main text" />
<!-- Table 2: Row 1: Column 2: (date) -->
<TextView
android:id="@+id/list_item_date"
style="@style/ListItemDate"
tools:text="2020-09-09" />
</TableRow>
<TableRow>
<!-- Need to match the style set in ListItemUtils.setListItem -->
<!-- Table 2: Row 2: Column 1: (shared owner, total time, total distance, and marker count) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom">
<TextView
android:id="@+id/list_item_time_distance"
style="@style/TextSmall"
android:layout_gravity="bottom"
android:ellipsize="end"
android:singleLine="true"
tools:text="50:43 (50.00km)" />
<ImageView
android:id="@+id/list_item_marker_count_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:layout_marginBottom="1dp"
android:contentDescription="@string/image_marker_count"
android:scaleType="centerInside"
android:src="@drawable/ic_marker_show_24dp" />
<TextView
android:id="@+id/list_item_marker_count"
style="@style/TextSmall"
android:layout_gravity="bottom"
android:ellipsize="end"
android:singleLine="true"
tools:text="2" />
</LinearLayout>
<!-- Table 2: Row 2: Column 2: (time) -->
<TextView
android:id="@+id/list_item_time"
style="@style/ListItemDate" />
</TableRow>
</TableLayout>
</TableRow>
<TableRow>
<!-- Table 1: Row 2: (category and description) -->
<TextView
android:id="@+id/list_item_category_description"
style="@style/TextSmall"
android:layout_column="1"
android:ellipsize="end"
android:maxLines="2"
android:singleLine="false"
tools:text="description" />
</TableRow>
</TableLayout>
</FrameLayout>
<View
<!-- A gradient on top of the text -->
<ImageView
android:id="@+id/list_item_text_gradient"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/activatedBackgroundIndicator" />
</FrameLayout>
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:alpha=".6"
android:visibility="gone"
tools:visibility="visible"
android:contentDescription="@string/image_photo_marker"
android:src="@drawable/text_gradient" />
<!-- Icon -->
<ImageView
android:id="@+id/list_item_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:contentDescription="@string/image_track"
tools:src="@drawable/ic_activity_bike_24dp"/>
<!-- Name -->
<TextView
android:id="@+id/list_item_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceBody1"
android:singleLine="true"
android:layout_marginStart="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/list_item_icon"
app:layout_constraintEnd_toStartOf="@id/list_item_date"
tools:text="Main text" />
<!-- Date -->
<TextView
android:id="@+id/list_item_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/textAppearanceBodySmall"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/list_item_name"
app:layout_constraintEnd_toEndOf="parent"
android:gravity="end"
android:layout_marginStart="8dp"
tools:text="2022-02-17" />
<!-- Total time and distance -->
<TextView
android:id="@+id/list_item_time_distance"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceBodySmall"
app:layout_constraintTop_toBottomOf="@id/list_item_name"
app:layout_constraintStart_toStartOf="@id/list_item_name"
tools:text="50:43 (50.00km)" />
<!-- Marker count icon -->
<ImageView
android:id="@+id/list_item_marker_count_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/list_item_name"
app:layout_constraintStart_toEndOf="@id/list_item_time_distance"
app:layout_constraintBottom_toBottomOf="@id/list_item_time_distance"
android:contentDescription="@string/image_marker_count"
android:scaleType="centerInside"
android:src="@drawable/ic_marker_show_24dp" />
<!-- Marker count text -->
<TextView
android:id="@+id/list_item_marker_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/textAppearanceBodySmall"
app:layout_constraintStart_toEndOf="@id/list_item_marker_count_icon"
app:layout_constraintBottom_toBottomOf="@id/list_item_marker_count_icon"
android:singleLine="true"
tools:text="2" />
<!-- Time -->
<TextView
android:id="@+id/list_item_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceBodySmall"
app:layout_constraintTop_toBottomOf="@id/list_item_name"
app:layout_constraintStart_toEndOf="@id/list_item_marker_count"
app:layout_constraintEnd_toEndOf="parent"
android:gravity="end"
android:layout_gravity="bottom"
android:layout_marginStart="8dp"
tools:text="10:10:10"/>
<!-- Second row barrier -->
<androidx.constraintlayout.widget.Barrier
android:id="@+id/list_item_second_row_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="list_item_time_distance,list_item_marker_count_icon,list_item_marker_count,list_item_time" />
<!-- Category and description -->
<TextView
android:id="@+id/list_item_category_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/textAppearanceBodySmall"
app:layout_constraintTop_toBottomOf="@id/list_item_second_row_barrier"
app:layout_constraintStart_toStartOf="@id/list_item_name"
app:layout_constraintEnd_toEndOf="parent"
android:maxLines="2"
android:ellipsize="end"
tools:text="Description" />
</androidx.constraintlayout.widget.ConstraintLayout>
+19 -6
View File
@@ -15,7 +15,8 @@ limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:id="@+id/marker_detail_marker_photo"
@@ -46,20 +47,32 @@ limitations under the License.
<TextView
android:id="@+id/marker_detail_marker_name"
style="@style/TextMedium"
android:paddingTop="8dp" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/textAppearanceBody1"
android:paddingTop="8dp"
tools:text="Marker Name" />
<TextView
android:id="@+id/marker_detail_marker_category"
style="@style/TextSmall" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/textAppearanceBody2"
tools:text="Marker Category" />
<TextView
android:id="@+id/marker_detail_marker_description"
style="@style/TextSmall" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/textAppearanceBody2"
tools:text="Marker Description" />
<TextView
android:id="@+id/marker_detail_marker_location"
style="@style/TextSmall" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/textAppearanceBody2"
tools:text="Marker Location" />
</LinearLayout>
</FrameLayout>
</FrameLayout>
+15 -6
View File
@@ -31,40 +31,48 @@ limitations under the License.
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textInputOutlinedStyle"
android:hint="@string/generic_name"
android:imeOptions="actionNext"
android:importantForAutofill="no">
android:importantForAutofill="no"
android:layout_marginBottom="16dp">
<com.google.android.material.textfield.TextInputEditText
android:drawableStart="@drawable/ic_marker_orange_pushpin_with_shadow"
android:id="@+id/marker_edit_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_marker_orange_pushpin_with_shadow"
android:inputType="textCapWords"
android:selectAllOnFocus="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textInputOutlinedExposedDropdownMenuStyle"
android:imeOptions="actionNext"
android:importantForAutofill="no">
android:importantForAutofill="no"
android:layout_marginBottom="16dp">
<AutoCompleteTextView
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/marker_edit_marker_type"
style="@style/EditCategory"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/marker_edit_marker_type"
android:imeOptions="actionNext"
android:selectAllOnFocus="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textInputOutlinedStyle"
android:hint="@string/generic_description"
android:imeOptions="actionNext"
android:importantForAutofill="no">
android:importantForAutofill="no"
android:layout_marginBottom="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/marker_edit_description"
@@ -72,6 +80,7 @@ limitations under the License.
android:layout_height="wrap_content"
android:inputType="textCapSentences"
android:selectAllOnFocus="true" />
</com.google.android.material.textfield.TextInputLayout>
<RelativeLayout
+5 -1
View File
@@ -27,6 +27,10 @@ limitations under the License.
<TextView
android:id="@+id/marker_list_empty"
style="@style/ListEmptyTextView"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="?attr/textAppearanceHeadline4"
android:gravity="center_horizontal|center_vertical"
android:text="@string/marker_list_empty_message" />
</LinearLayout>
@@ -14,7 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/ScrollView">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbarStyle="outsideInset">
<LinearLayout
android:layout_width="match_parent"
@@ -23,18 +26,33 @@ limitations under the License.
android:paddingTop="16dp"
android:paddingEnd="8dp">
<AutoCompleteTextView
android:id="@+id/activity_type_preference_text_view"
style="@style/EditCategory"
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/track_edit_activity_type_hint" />
style="?attr/textInputOutlinedStyle"
android:imeOptions="actionNext"
android:importantForAutofill="no"
android:layout_marginEnd="8dp">
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/activity_type_preference_text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:hint="@string/track_edit_activity_type_hint"
android:imeOptions="actionNext"
android:singleLine="true"
android:layout_gravity="bottom"
android:selectAllOnFocus="true" />
</com.google.android.material.textfield.TextInputLayout>
<ImageView
android:id="@+id/activity_type_preference_spinner"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="bottom" />
android:layout_gravity="bottom"
tools:src="@drawable/ic_activity_bike_24dp"/>
</LinearLayout>
</ScrollView>
+97 -37
View File
@@ -2,13 +2,22 @@
<ScrollView 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">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbarStyle="outsideOverlay">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Guidelines -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_top"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintGuide_begin="16dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="0dp"
@@ -33,25 +42,29 @@
<!-- Activity information: icon, name, description and start datetime -->
<ImageView
android:id="@+id/stats_activity_type_icon"
style="@style/IconStats"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toTopOf="@id/stats_name_value"
app:layout_constraintTop_toTopOf="@id/guideline_top"
tools:src="@drawable/ic_activity_bike_24dp" />
<TextView
android:id="@+id/stats_name_value"
style="@style/TextLargeStats"
style="?attr/textAppearanceHeadline6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/stats_activity_type_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toTopOf="@id/guideline_top"
tools:text="Name of the activity" />
<TextView
android:id="@+id/stats_description_value"
style="@style/TextMediumStats"
style="?attr/textAppearanceBody1"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toStartOf="@id/stats_name_value"
@@ -60,8 +73,11 @@
<TextView
android:id="@+id/stats_start_datetime_value"
style="@style/TextSmallStats"
style="?attr/textAppearanceBody2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="end"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toStartOf="@id/guideline"
@@ -79,8 +95,8 @@
<!-- Horizontal line -->
<View
android:id="@+id/stats_information_horizontal_line"
style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp"
style="@style/HorizontalLine"
android:layout_margin="8dp"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_start_datetime_value" />
@@ -88,7 +104,9 @@
<!-- Distance -->
<TextView
android:id="@+id/stats_distance_label"
style="@style/LargeLabel"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/stats_distance"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline"
@@ -97,7 +115,9 @@
<TextView
android:id="@+id/stats_distance_value"
style="@style/StatsPrimaryValue"
style="@style/TextAppearance.OpenTracks.PrimaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
@@ -107,7 +127,9 @@
<TextView
android:id="@+id/stats_distance_unit"
style="@style/StatsUnit"
style="@style/TextAppearance.OpenTracks.PrimaryUnit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/stats_distance_value"
app:layout_constraintStart_toEndOf="@id/stats_distance_value"
app:layout_constraintEnd_toStartOf="@id/guideline2"
@@ -116,7 +138,9 @@
<!-- Moving time -->
<TextView
android:id="@+id/stats_moving_time_label"
style="@style/LargeLabel"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/stats_moving_time"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
@@ -125,7 +149,9 @@
<TextView
android:id="@+id/stats_moving_time_value"
style="@style/StatsPrimaryValue"
style="@style/TextAppearance.OpenTracks.PrimaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:value="@string/value_unknown"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
@@ -135,7 +161,9 @@
<!-- Total time -->
<TextView
android:id="@+id/stats_total_time_label"
style="@style/StatsSmallLabel"
style="@style/TextAppearance.OpenTracks.SecondaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/stats_total_time"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
@@ -144,7 +172,9 @@
<TextView
android:id="@+id/stats_total_time_value"
style="@style/StatsSmallValue"
style="@style/TextAppearance.OpenTracks.SecondaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_total_time_label"
@@ -161,8 +191,8 @@
<!-- Horizontal line -->
<View
android:id="@+id/stats_speed_horizontal_line"
style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp"
style="@style/HorizontalLine"
android:layout_margin="8dp"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_time_barrier" />
@@ -170,7 +200,9 @@
<!-- Speed/Pace max -->
<TextView
android:id="@+id/stats_max_speed_label"
style="@style/LargeLabel"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline2"
@@ -179,7 +211,9 @@
<TextView
android:id="@+id/stats_max_speed_value"
style="@style/StatsPrimaryValue"
style="@style/TextAppearance.OpenTracks.PrimaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
@@ -189,7 +223,9 @@
<TextView
android:id="@+id/stats_max_speed_unit"
style="@style/StatsUnit"
style="@style/TextAppearance.OpenTracks.PrimaryUnit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/stats_max_speed_value"
app:layout_constraintStart_toEndOf="@id/stats_max_speed_value"
app:layout_constraintEnd_toStartOf="@id/guideline2"
@@ -198,7 +234,9 @@
<!-- Avg. moving speed/pace -->
<TextView
android:id="@+id/stats_moving_speed_label"
style="@style/LargeLabel"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
@@ -207,7 +245,9 @@
<TextView
android:id="@+id/stats_moving_speed_value"
style="@style/StatsPrimaryValue"
style="@style/TextAppearance.OpenTracks.PrimaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
@@ -217,7 +257,9 @@
<TextView
android:id="@+id/stats_moving_speed_unit"
style="@style/StatsUnit"
style="@style/TextAppearance.OpenTracks.PrimaryUnit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/stats_moving_speed_value"
app:layout_constraintStart_toEndOf="@id/stats_moving_speed_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
@@ -226,7 +268,9 @@
<!-- Average speed/pace -->
<TextView
android:id="@+id/stats_average_speed_label"
style="@style/StatsSmallLabel"
style="@style/TextAppearance.OpenTracks.SecondaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
@@ -235,7 +279,9 @@
<TextView
android:id="@+id/stats_average_speed_value"
style="@style/StatsSmallValue"
style="@style/TextAppearance.OpenTracks.SecondaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
@@ -245,7 +291,9 @@
<TextView
android:id="@+id/stats_average_speed_unit"
style="@style/StatsUnit"
style="@style/TextAppearance.OpenTracks.PrimaryUnit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/stats_average_speed_value"
app:layout_constraintStart_toEndOf="@id/stats_average_speed_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
@@ -262,8 +310,8 @@
<!-- Horizontal line -->
<View
android:id="@+id/stats_altitude_horizontal_line"
style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp"
style="@style/HorizontalLine"
android:layout_margin="8dp"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_speed_barrier" />
@@ -277,7 +325,9 @@
<TextView
android:id="@+id/stats_altitude_gain_label"
style="@style/LargeLabel"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/stats_gain"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline"
@@ -286,7 +336,9 @@
<TextView
android:id="@+id/stats_altitude_gain_value"
style="@style/StatsPrimaryValue"
style="@style/TextAppearance.OpenTracks.PrimaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
@@ -296,7 +348,9 @@
<TextView
android:id="@+id/stats_altitude_gain_unit"
style="@style/StatsUnit"
style="@style/TextAppearance.OpenTracks.PrimaryUnit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/stats_altitude_gain_value"
app:layout_constraintStart_toEndOf="@id/stats_altitude_gain_value"
app:layout_constraintEnd_toStartOf="@id/guideline2"
@@ -304,7 +358,9 @@
<TextView
android:id="@+id/stats_altitude_loss_label"
style="@style/LargeLabel"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/stats_loss"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
@@ -313,7 +369,9 @@
<TextView
android:id="@+id/stats_altitude_loss_value"
style="@style/StatsPrimaryValue"
style="@style/TextAppearance.OpenTracks.PrimaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
@@ -323,7 +381,9 @@
<TextView
android:id="@+id/stats_altitude_loss_unit"
style="@style/StatsUnit"
style="@style/TextAppearance.OpenTracks.PrimaryUnit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/stats_altitude_loss_value"
app:layout_constraintStart_toEndOf="@id/stats_altitude_loss_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
@@ -340,8 +400,8 @@
<!-- Horizontal line -->
<View
android:id="@+id/stats_end_horizontal_line"
style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp"
style="@style/HorizontalLine"
android:layout_margin="8dp"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/stats_altitude_barrier" />
@@ -2,7 +2,6 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/StatsScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
+6 -2
View File
@@ -10,7 +10,9 @@
<TextClock
android:id="@+id/stats_clock"
style="@style/StatsPrimaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.OpenTracks.PrimaryValue"
android:value="@string/value_unknown"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -19,7 +21,9 @@
<TextView
android:id="@+id/stats_description_main"
style="@style/StatsPrimaryLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
android:text="@string/stats_clock"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
+12 -4
View File
@@ -10,7 +10,9 @@
<TextView
android:id="@+id/stats_value"
style="@style/StatsPrimaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.OpenTracks.PrimaryValue"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
@@ -20,7 +22,9 @@
<TextView
android:id="@+id/stats_unit"
style="@style/StatsUnit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.OpenTracks.PrimaryUnit"
tools:text="@string/sensor_unit_beats_per_minute"
app:layout_constraintStart_toEndOf="@id/stats_value"
app:layout_constraintEnd_toEndOf="parent"
@@ -28,7 +32,9 @@
<TextView
android:id="@+id/stats_description_main"
style="@style/StatsPrimaryLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
tools:text="@string/sensor_state_heart_rate"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -36,7 +42,9 @@
<TextView
android:id="@+id/stats_description_secondary"
style="@style/StatsTinyValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.OpenTracks.SecondaryHeader"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
+13 -6
View File
@@ -5,12 +5,13 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
android:paddingTop="16dp">
android:layout_marginBottom="16dp">
<TextView
android:id="@+id/stats_description_main"
style="@style/LargeLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
tools:text="@string/sensor_state_heart_rate"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -18,7 +19,9 @@
<TextView
android:id="@+id/stats_value"
style="@style/StatsPrimaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.OpenTracks.PrimaryValue"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/stats_description_main"
@@ -28,7 +31,9 @@
<TextView
android:id="@+id/stats_unit"
style="@style/StatsUnit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.OpenTracks.PrimaryUnit"
tools:text="@string/sensor_unit_beats_per_minute"
app:layout_constraintStart_toEndOf="@id/stats_value"
app:layout_constraintEnd_toEndOf="parent"
@@ -36,7 +41,9 @@
<TextView
android:id="@+id/stats_description_secondary"
style="@style/StatsTinyValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.OpenTracks.SecondaryHeader"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
+3 -4
View File
@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.appcompat.widget.Toolbar
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/Widget.MaterialComponents.Toolbar.Surface"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize" />
android:layout_height="wrap_content" />
</merge>
+20 -6
View File
@@ -16,6 +16,7 @@ limitations under the License.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<include layout="@layout/toolbar" />
@@ -31,9 +32,11 @@ limitations under the License.
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textInputOutlinedStyle"
android:hint="@string/generic_name"
android:imeOptions="actionNext"
android:importantForAutofill="no">
android:importantForAutofill="no"
android:layout_marginBottom="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/track_edit_name"
@@ -41,6 +44,7 @@ limitations under the License.
android:layout_height="wrap_content"
android:inputType="textCapWords"
android:selectAllOnFocus="true" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
@@ -52,30 +56,39 @@ limitations under the License.
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
style="?attr/textInputOutlinedExposedDropdownMenuStyle"
android:imeOptions="actionNext"
android:importantForAutofill="no">
android:importantForAutofill="no"
android:layout_marginBottom="16dp">
<AutoCompleteTextView
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/track_edit_activity_type"
style="@style/EditCategory"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:hint="@string/track_edit_activity_type_hint"
android:imeOptions="actionNext"
android:selectAllOnFocus="true" />
</com.google.android.material.textfield.TextInputLayout>
<ImageView
android:id="@+id/track_edit_activity_type_icon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="bottom" />
android:layout_gravity="bottom"
tools:src="@drawable/ic_activity_bike_24dp"/>
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/textInputOutlinedStyle"
android:hint="@string/generic_description"
android:imeOptions="actionNext"
android:importantForAutofill="no">
android:importantForAutofill="no"
android:layout_marginBottom="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/track_edit_description"
@@ -83,6 +96,7 @@ limitations under the License.
android:layout_height="wrap_content"
android:inputType="textCapSentences"
android:selectAllOnFocus="true" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
+1 -1
View File
@@ -69,7 +69,7 @@ limitations under the License.
android:layout_weight="5" />
<TextView
style="@style/TextLarge"
style="?attr/textAppearanceHeadline6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="22"
+5 -1
View File
@@ -473,7 +473,7 @@
<string name="settings_sensor_cycling_distance_speed">Distancia y velocidad</string>
<string name="settings_sensor_cycling_cadence">Cadencia</string>
<string name="track_delete_not_recording">Este recorrido no puede ser eliminado porque se está grabando actualmente.</string>
<string name="settings_defaults_title">Unidades y actividad predeterminadas</string>
<string name="settings_defaults_title">Unidades y Actividad predeterminadas</string>
<string name="export_cannot_write_to_dir">No se puede escribir al directorio de exportación.</string>
<string name="export_dir_not_writable">" (¡No se puede escribir!)"</string>
<string name="value_float_mile_hour">%1$.1f mi/h</string>
@@ -533,4 +533,8 @@
<string name="no_compatible_file_manager_installed">No hay un gestor de archivos compatible instalado</string>
<string name="settings_running_sensor">Correr</string>
<string name="settings_sensor_bluetooth_service_filter_enabled">Mostrar sólo los dispositivos Bluetooth que anuncian los servicios requeridos</string>
<string name="settings_api_title">API pública</string>
<string name="settings_public_api_dashboard_enabled_title">Transferencia automática de datos</string>
<string name="settings_public_api_dashboard_enabled_summary_off">Los datos grabados no se compartirán automáticamente con otras aplicaciones.</string>
<string name="settings_public_api_dashboard_enabled_summary_on">Una aplicación que ha iniciado una grabación puede también acceder a los datos grabados.</string>
</resources>
+31 -3
View File
@@ -450,7 +450,7 @@ limitations under the License.
<string name="settings_night_mode_title">Vzhled</string>
<string name="settings_night_mode_option_day">Světlý</string>
<string name="settings_night_mode_option_night">Tmavý</string>
<string name="settings_sensor_wheel_circumference">Obvod kolečka (mm)</string>
<string name="settings_sensor_wheel_circumference">Obvod kola (mm)</string>
<string name="stats_split_interval">Rozdělit interval</string>
<string name="track_detail_intervals_tab">Intervaly</string>
<string name="voice_pace_lap">Čas na kolo %1$s</string>
@@ -493,7 +493,7 @@ limitations under the License.
<string name="export_progress_summary_new_msg">Vyvezeny nové tratě</string>
<string name="import_progress_summary_exists_msg">Existovat</string>
<string name="import_progress_summary_ok_msg">Importované soubory</string>
<string name="generic_open_track">Otevřená Trať</string>
<string name="generic_open_track">Otevřená trať</string>
<string name="sensor_state_power_avg">Výkon Avg</string>
<string name="sensor_state_heart_rate_max">Maximální srdeční frekvence</string>
<string name="sensor_state_heart_rate_avg">Avg srdeční frekvence</string>
@@ -557,7 +557,7 @@ limitations under the License.
<string name="settings_sensors_title">Bluetooth senzory</string>
<string name="settings_gps_summary">Časový interval, Interval vzdálenosti, Maximální vzdálenost, Přesnost</string>
<string name="settings_ui_summary">Vzhled, Rozložení záznamu, Téma</string>
<string name="settings_open_tracks_title">Otevřené stopy</string>
<string name="settings_open_tracks_title">O programu</string>
<string name="settings_open_tracks_summary">Vše o projektu OpenTracks</string>
<string name="settings_defaults_title">Výchozí jednotky a aktivity</string>
<string name="settings_ui_title">Uživatelské rozhraní</string>
@@ -582,4 +582,32 @@ limitations under the License.
<string name="export_dir_not_writable">" (nelze zapisovat!)"</string>
<string name="generic_today">Dnes</string>
<string name="no_compatible_file_manager_installed">Není nainstalován kompatibilní správce souborů</string>
<string name="settings_announcements_report_heart_rate">Hlásit tepovou frekvenci</string>
<string name="voice_per_kilometer">za kilometr</string>
<string name="lap_time">Čas kola</string>
<string name="lap_speed">Rychlost kola</string>
<string name="speed">Rychlost</string>
<string name="total_distance">Celková vzdálenost</string>
<plurals name="voiceDistanceKilometers">
<item quantity="one"></item>
<item quantity="few"></item>
<item quantity="other"></item>
</plurals>
<string name="average_heart_rate">Průměrný srdeční tep</string>
<string name="current_heart_rate">Aktuální srdeční tep</string>
<string name="settings_public_api_enabled_summary_on">Ostatní nainstalované aplikace mohou nahrávání spustit nebo zastavit.</string>
<string name="voice_per_mile">za míli</string>
<string name="activity_type_swimming">plavání</string>
<string name="activity_type_swimming_open">plavání ve volné vodě</string>
<string name="activity_type_workout">trénink</string>
<string name="generic_dismiss">Odmítnout</string>
<string name="gps_recording_status">%1$s: %2$s</string>
<string name="gps_recording_without_signal">OpenTracks nahrává bez údajů GPS.</string>
<string name="settings_public_api_enabled_title">Veřejné API</string>
<string name="settings_public_api_enabled_summary_off">Pouze aplikace OpenTracks může spouštět a zastavovat nahrávání.</string>
<string name="settings_public_api_disabled_toast">Veřejné rozhraní API služby OpenTracks je zakázáno: lze jej povolit v nastavení.</string>
<string name="settings_api_title">Veřejné API</string>
<string name="settings_public_api_dashboard_enabled_title">Automatický přenos dat</string>
<string name="settings_public_api_dashboard_enabled_summary_on">Aplikace, která zahájila nahrávání, může také přistupovat k nahraným datům.</string>
<string name="settings_public_api_dashboard_enabled_summary_off">Zaznamenaná data nebudou automaticky sdílena s jinými aplikacemi.</string>
</resources>
+5 -1
View File
@@ -569,7 +569,7 @@ limitations under the License.
<string name="settings_announcements_summary">Tiempo, Distancia, Velocidad de la voz</string>
<string name="settings_sensor_bluetooth_service_filter_enabled">Mostrar sólo los dispositivos Bluetooth que anuncian los servicios requeridos</string>
<string name="settings_open_tracks_summary">Todo sobre el proyecto OpenTracks</string>
<string name="settings_defaults_title">Unidades y actividad predeterminadas</string>
<string name="settings_defaults_title">Unidades y Actividad predeterminadas</string>
<string name="settings_defaults_summary">Unidades preferidas, Actividad predeterminada, Nombre predeterminado del recorrido</string>
<string name="settings_open_tracks_title">Open Tracks</string>
<string name="generic_undo">Deshacer</string>
@@ -584,4 +584,8 @@ limitations under the License.
<string name="generic_dismiss">Descartar</string>
<string name="gps_recording_status">%1$s: %2$s</string>
<string name="gps_recording_without_signal">OpenTracks está grabando sin datos GPS.</string>
<string name="settings_api_title">API pública</string>
<string name="settings_public_api_dashboard_enabled_title">Transferencia automática de datos</string>
<string name="settings_public_api_dashboard_enabled_summary_on">Una aplicación que ha iniciado una grabación puede también acceder a los datos grabados.</string>
<string name="settings_public_api_dashboard_enabled_summary_off">Los datos grabados no se compartirán automáticamente con otras aplicaciones.</string>
</resources>
+20 -2
View File
@@ -85,8 +85,8 @@ limitations under the License.
<string name="description_activity_type">Aktivitetstype: %1$s</string>
<string name="description_average_moving_pace_in_minute">Gjennomsnittlig forflytningstempo: %1$s min/km (%2$s min/mi)</string>
<string name="description_average_moving_speed">Gjennomsnittlig forflytningshastighet: %1$.2f km/t (%2$.1f mi/h)</string>
<string name="description_average_pace_in_minute">Gjennomsnittstempo: %1$s min/km (%2$s min/mi)</string>
<string name="description_average_speed">Gjennomsnittshastighet: %1$.2f km/t (%2$.1f mi/h)</string>
<string name="description_average_pace_in_minute">Gjennomsnittlig tempo: %1$s min/km (%2$s min/mi)</string>
<string name="description_average_speed">Gjennomsnittlig hastighet: %1$.2f km/t (%2$.1f mi/h)</string>
<string name="description_altitude_gain">Total stigning: %1$d m (%2$d ft)</string>
<string name="description_altitude_imperial">Høyde (ft)</string>
<string name="description_altitude_metric">Høyde (m)</string>
@@ -584,4 +584,22 @@ limitations under the License.
<string name="settings_public_api_dashboard_enabled_title">Automatisk dataoverføring</string>
<string name="settings_public_api_dashboard_enabled_summary_on">Et program som startet et opptak kan også få tilgang til de registrerte dataene.</string>
<string name="settings_public_api_dashboard_enabled_summary_off">Registrerte data blir ikke delt automatisk med andre programmer.</string>
<string name="settings_announcements_report_heart_rate">Rapporter puls</string>
<string name="voice_per_kilometer">per kilometer</string>
<string name="voice_per_mile">per engelsk mil</string>
<string name="lap_time">Rundetid</string>
<string name="pace">Tempo</string>
<string name="lap_speed">Rundehastighet</string>
<string name="speed">Hastighet</string>
<string name="total_distance">Total distanse</string>
<plurals name="voiceDistanceMiles">
<item quantity="one">1 engelsk mil</item>
<item quantity="other">%1$.2f engelske mil</item>
</plurals>
<string name="average_heart_rate">Gjennomsnittlig puls</string>
<plurals name="voiceDistanceKilometers">
<item quantity="one">1 kilometer</item>
<item quantity="other">%1$.2f kilometer</item>
</plurals>
<string name="current_heart_rate">Gjeldende puls</string>
</resources>
-5
View File
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorAccent">@android:color/holo_orange_dark</color>
<color name="colorAccentTransparent">#55ff8800</color>
</resources>
@@ -0,0 +1,25 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="ThemeCustom" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/opentracks_secondary_color</item>
<item name="colorPrimaryVariant">@android:color/black</item>
<item name="colorOnPrimary">@android:color/white</item>
<item name="colorSecondary">@color/opentracks_secondary_color</item>
<item name="colorSecondaryVariant">@color/opentracks_secondary_color</item>
<item name="colorOnSecondary">@color/opentracks_on_secondary_color</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<item name="windowActionModeOverlay">true</item>
<item name="android:colorBackground">@android:color/black</item>
<item name="colorOnBackground">@android:color/white</item>
<item name="colorSurface">@color/opentracks_surface_night_background_color</item>
<item name="colorOnSurface">@color/opentracks_surface_night_on_background_color</item>
<item name="android:activatedBackgroundIndicator">@drawable/activated_background</item>
</style>
</resources>
+31 -1
View File
@@ -380,7 +380,7 @@ limitations under the License.
<string name="generic_overwrite">Ghi đè</string>
<string name="menu_voice_rate">Tốc độ giọng nói</string>
<string name="open_track_as_GPX">Mở dưới dạng GPX</string>
<string name="help_recording_content">OpenTracks sử dụng các bộ cảm biến GPS của điện thoại để ghi lại vị trí liên tục. Nếu các thiết bị GPS báo cáo dữ liệu không chính xác (ví dụ như, vị trí, tốc độ, độ cao), OpenTracks không thể làm bất cứ điều gì về nó.</string>
<string name="help_recording_content">Mở bài Hát sử dụng các bộ cảm biến GPS của điện thoại để ghi lại vị trí liên tục. Nếu các thiết bị GPS báo cáo dữ liệu không chính xác (ví dụ như, vị trí, tốc độ, độ cao), OpenTracks không thể làm bất cứ điều gì về nó.</string>
<string name="gps_fixed_and_ready">GPS đã xác định và sẵn sàng</string>
<string name="generic_skip">Bỏ qua</string>
<string name="import_unsupported_format">Định dạng tệp không được hỗ trợ</string>
@@ -562,4 +562,34 @@ limitations under the License.
<string name="export_dir_not_writable">" (không ghi được!)"</string>
<string name="export_cannot_write_to_dir">Không thể viết để xuất khẩu, xuất khẩu.</string>
<string name="no_compatible_file_manager_installed">Không có tương thích quản lý tập tin cài đặt</string>
<string name="activity_type_swimming">bơi</string>
<string name="activity_type_workout">thể dục</string>
<string name="settings_public_api_enabled_title">API công khai</string>
<string name="settings_public_api_enabled_summary_off">Chỉ OpenTracks có thể bắt đầu và dừng ghi.</string>
<string name="settings_public_api_enabled_summary_on">Các ứng dụng khác có thể bắt đầu hoặc dừng ghi.</string>
<string name="settings_announcements_report_heart_rate">Báo cáo nhịp tim</string>
<string name="settings_api_title">API công khai</string>
<string name="generic_dismiss">Bỏ</string>
<string name="gps_recording_status">%1$s: %2$s</string>
<string name="gps_recording_without_signal">OpenTracks đang ghi lại mà không có dữ liệu GPS.</string>
<string name="activity_type_swimming_open">bơi trong mở nước</string>
<string name="voice_per_kilometer">mỗi km</string>
<string name="total_distance">Tổng khoảng cách</string>
<string name="settings_public_api_disabled_toast">Mở bài Hát Công cộng API là vô hiệu hóa nó có thể được kích hoạt trong các thiết lập.</string>
<string name="voice_per_mile">mỗi dặm</string>
<string name="lap_time">Thời gian lòng</string>
<string name="pace">Tốc độ</string>
<string name="lap_speed">Vòng tốc độ</string>
<string name="speed">Tốc độ</string>
<plurals name="voiceDistanceKilometers">
<item quantity="other">%1$.2f Tầng km</item>
</plurals>
<plurals name="voiceDistanceMiles">
<item quantity="other">%1$.2f dặm</item>
</plurals>
<string name="average_heart_rate">Nhịp tim trung bình</string>
<string name="current_heart_rate">Hiện tại nhịp tim</string>
<string name="settings_public_api_dashboard_enabled_summary_on">Một ứng dụng bắt đầu ghi hình cũng có thể truy cập vào dữ liệu ghi lại.</string>
<string name="settings_public_api_dashboard_enabled_summary_off">Ghi lại dữ liệu sẽ không được miễn phí tự động với các ứng dụng khác.</string>
<string name="settings_public_api_dashboard_enabled_title">Chuyển dữ liệu tự động</string>
</resources>
+19 -2
View File
@@ -14,6 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Brand colors -->
<color name="opentracks_primary_color">#344955</color>
<color name="opentracks_primary_variant_color">#232f34</color>
<color name="opentracks_on_primary_color">@android:color/white</color>
<color name="opentracks_secondary_color">#ff8800</color>
<color name="opentracks_secondary_transparent">#55ff8800</color>
<color name="opentracks_on_secondary_color">@android:color/white</color>
<color name="opentracks_surface_day_background_color">#f5f5f5</color>
<color name="opentracks_surface_day_on_background_color">@android:color/black</color>
<color name="opentracks_surface_night_background_color">@android:color/black</color>
<color name="opentracks_surface_night_on_background_color">@android:color/white</color>
<!-- Chart colors -->
<color name="chart_cadence_border">#FFF128F3</color> <!-- pink -->
<color name="chart_cadence_fill">#40F128F3</color> <!-- transparent pink -->
<color name="chart_altitude_border">#FF339933</color> <!-- green -->
@@ -27,8 +42,10 @@ limitations under the License.
<color name="chart_power_border">#FF7A26C0</color> <!-- purple -->
<color name="chart_power_fill">#407A26C0</color> <!-- transparent purple -->
<color name="colorAccent">@android:color/holo_orange_dark</color>
<color name="colorAccentTransparent">#55FF8800</color>
<!-- Generic colors -->
<color name="red_dark">#ffbe161b</color>
<color name="light_gray">#aaaaaa</color>
<color name="colorBackgroundDelete">#F44336</color>
<color name="colorForegroundDelete">#FEFEFE</color>
+1
View File
@@ -283,6 +283,7 @@
<item>1.2</item>
<item>1.3</item>
</string-array>
<string name="voice_announce_heart_rate_key" translatable="false">voiceAnnounceHeartRate</string>
<string name="export_trackfileformat_key" translatable="false">exportTrackFileFormat</string>
<!-- See TrackFileFormat -->
+18
View File
@@ -405,6 +405,7 @@ limitations under the License.
<string name="settings_announcements">Announcements</string>
<string name="settings_announcements_totaltime_frequency">Time interval</string>
<string name="settings_announcements_distance_frequency">Distance interval</string>
<string name="settings_announcements_report_heart_rate">Report heart rate</string>
<string name="settings_reset">Reset</string>
<!-- Custom Layout -->
@@ -574,6 +575,13 @@ limitations under the License.
<string name="voice_template">%1$s in %2$s at %3$s</string>
<string name="voice_speed_lap">Lap speed of %1$s</string>
<string name="voice_pace_lap">Lap time of %1$s</string>
<string name="voice_per_kilometer">per kilometer</string>
<string name="voice_per_mile">per mile</string>
<string name="lap_time">Lap time</string>
<string name="pace">Pace</string>
<string name="lap_speed">Lap speed</string>
<string name="speed">Speed</string>
<string name="total_distance">Total distance</string>
<plurals name="voiceTotalDistanceKilometers">
<item quantity="one">total distance 1 kilometer</item>
<item quantity="other">total distance %1$.2f kilometers</item>
@@ -582,6 +590,16 @@ limitations under the License.
<item quantity="one">total distance 1 mile</item>
<item quantity="other">total distance %1$.2f miles</item>
</plurals>
<plurals name="voiceDistanceKilometers">
<item quantity="one">1 kilometer</item>
<item quantity="other">%1$.2f kilometers</item>
</plurals>
<plurals name="voiceDistanceMiles">
<item quantity="one">1 mile</item>
<item quantity="other">%1$.2f miles</item>
</plurals>
<string name="average_heart_rate">Average heart rate</string>
<string name="current_heart_rate">Current heart rate</string>
<!-- Waypoint Type -->
<string name="waypoint_type_atm">ATM</string>
<string name="waypoint_type_bank">bank</string>
+14 -274
View File
@@ -15,7 +15,13 @@ limitations under the License.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="BottomButton" parent="Widget.AppCompat.Button.Borderless">
<!-- toolbar -->
<style name="Widget.OpenTracks.Toolbar.Surface" parent="Widget.MaterialComponents.Toolbar.Surface">
<item name="android:background">#ff0000</item>
</style>
<!-- bottom bar -->
<style name="BottomButton" parent="Widget.MaterialComponents.Button.TextButton">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_width">0dp</item>
@@ -36,70 +42,20 @@ limitations under the License.
<item name="android:layout_width">match_parent</item>
</style>
<style name="EditCategory" parent="@style/TextMedium">
<item name="android:completionThreshold">1</item>
<item name="android:gravity">bottom</item>
<item name="android:layout_gravity">bottom</item>
<item name="android:layout_width">match_parent</item>
<item name="android:selectAllOnFocus">true</item>
<item name="android:singleLine">true</item>
</style>
<style name="EditDescription" parent="@style/TextMedium">
<item name="android:inputType">textAutoCorrect|textCapSentences|textMultiLine</item>
<item name="android:layout_width">match_parent</item>
<item name="android:maxLines">50</item>
<item name="android:selectAllOnFocus">true</item>
<item name="android:singleLine">false</item>
</style>
<style name="EditLabel" parent="@style/TextMedium">
<item name="android:layout_width">match_parent</item>
</style>
<style name="EditLabel.WithPaddingTop">
<item name="android:paddingTop">16dp</item>
</style>
<style name="EditNameMarker" parent="@style/TextMedium.WithIcon">
<item name="android:inputType">textAutoCorrect|textCapWords</item>
<item name="android:layout_width">match_parent</item>
<item name="android:selectAllOnFocus">true</item>
<item name="android:singleLine">true</item>
</style>
<style name="EditNameTrack" parent="@style/TextMedium">
<item name="android:inputType">textAutoCorrect|textCapWords</item>
<item name="android:layout_width">match_parent</item>
<item name="android:selectAllOnFocus">true</item>
<item name="android:singleLine">true</item>
</style>
<style name="ListEmptyTextView" parent="@style/TextLarge">
<item name="android:gravity">center</item>
<item name="android:layout_gravity">center</item>
<item name="android:padding">8dp</item>
</style>
<style name="ListItemDate" parent="@style/TextSmall">
<item name="android:gravity">right</item>
<item name="android:layout_gravity">bottom</item>
<item name="android:paddingLeft">8dp</item>
<item name="android:singleLine">true</item>
</style>
<!-- scroll view -->
<style name="ScrollView">
<item name="android:layout_height">match_parent</item>
<item name="android:layout_width">match_parent</item>
<item name="android:scrollbarStyle">outsideInset</item>
</style>
<!-- scroll view with bottom buttons -->
<style name="ScrollView.WithBottomButtons">
<item name="android:layout_height">0dp</item>
<item name="android:layout_weight">1</item>
</style>
<!-- sensor state value -->
<!-- stats horizontal line -->
<!-- horizontal line -->
<style name="HorizontalLine">
<item name="android:background">@color/stats_separator</item>
<item name="android:layout_height">1dp</item>
@@ -107,225 +63,9 @@ limitations under the License.
<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>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_width">0dp</item>
<item name="android:orientation">vertical</item>
<item name="android:paddingBottom">6dp</item>
<item name="android:paddingTop">6dp</item>
</style>
<style name="LargeLabel" parent="@style/TextSmall">
<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">
<item name="android:singleLine">true</item>
<item name="android:layout_margin">1dp</item>
<item name="android:textAlignment">textStart</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_width">1dp</item>
<item name="android:layout_gravity">center</item>
</style>
<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: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="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">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>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginTop">-4dp</item>
<item name="android:layout_width">wrap_content</item>
</style>
<!-- stats scroll view -->
<style name="StatsScrollView" parent="@style/ScrollView">
<item name="android:scrollbarStyle">outsideOverlay</item>
</style>
<!-- stats small group container -->
<style name="StatsSmallGroupContainer" parent="@style/StatsLargeItemContainer">
<item name="android:layout_gravity">top|center</item>
<item name="android:paddingTop">12dp</item>
</style>
<!-- stats small item container, for placing inside StatsSmallGroupContainer -->
<style name="StatsSmallItemContainer" parent="@style/StatsLargeItemContainer">
<item name="android:paddingTop">12dp</item>
</style>
<style name="StatsSmallLabel" parent="@style/TextSmall">
<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:textStyle">bold</item>
<item name="android:layout_marginTop">16dp</item>
</style>
<style name="StatsSmallValue" parent="@style/TextLarge">
<item name="android:layout_gravity">center</item>
<item name="android:singleLine">true</item>
</style>
<style name="StatsTinyValue" parent="@style/TextSmall">
<item name="android:layout_gravity">center</item>
<item name="android:singleLine">true</item>
</style>
<style name="StatsTinyLabel" parent="@style/TextSmall">
<item name="android:gravity">center</item>
<item name="android:layout_gravity">center</item>
<item name="android:singleLine">false</item>
</style>
<style name="StatsSmallValueContainer" parent="@style/StatsLargeValueContainer">
<item name="android:layout_marginTop">-2dp</item>
</style>
<style name="StatsUnit" parent="@style/TextSmall">
<item name="android:layout_gravity">bottom</item>
<item name="android:paddingLeft">4dp</item>
<item name="android:singleLine">true</item>
</style>
<style name="TextLargeStats" parent="TextAppearance.AppCompat.Large">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:paddingLeft">8dp</item>
<item name="android:paddingRight">4dp</item>
<item name="android:layout_marginTop">16dp</item>
</style>
<style name="TextMediumStats" parent="TextAppearance.AppCompat.Medium">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:paddingLeft">8dp</item>
<item name="android:paddingRight">4dp</item>
<item name="android:color">@android:color/secondary_text_light</item>
</style>
<style name="TextSmallStats" parent="TextAppearance.AppCompat.Small">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:paddingLeft">8dp</item>
<item name="android:paddingRight">4dp</item>
<item name="android:paddingTop">8dp</item>
<item name="android:textAlignment">viewEnd</item>
<item name="android:color">@android:color/secondary_text_light</item>
</style>
<style name="IconStats">
<item name="android:layout_width">48dp</item>
<item name="android:layout_height">48dp</item>
<item name="tint">?android:textColorPrimary</item>
</style>
<style name="TextMicro" parent="@style/TextSmall">
<item name="android:textSize">12sp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
</style>
<style name="TextLarge" parent="TextAppearance.AppCompat.Large">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
</style>
<style name="TextMedium" parent="TextAppearance.AppCompat.Medium">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
</style>
<style name="TextMediumPrimary" parent="@style/TextMedium">
<item name="android:textColor">?android:textColorPrimary</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
</style>
<style name="TextMedium.WithIcon" style="@style/TextMedium">
<item name="android:drawablePadding">8dp</item>
<item name="android:gravity">center_vertical</item>
</style>
<style name="TextSmall" parent="TextAppearance.AppCompat.Small">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
</style>
<style name="TextImportExportSummaryStart" parent="TextMediumPrimary">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginTop">12dp</item>
<item name="android:layout_marginEnd">8dp</item>
<item name="android:layout_marginStart">24dp</item>
<item name="android:gravity">end</item>
</style>
<style name="TextImportExportSummaryEnd" parent="TextMediumPrimary">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginStart">8dp</item>
<item name="android:layout_marginEnd">24dp</item>
</style>
<style name="ProgressBar" parent="@style/Widget.AppCompat.ProgressBar.Horizontal" />
<!-- Card View for stats -->
<style name="StatsCardView">
<!-- card view -->
<style name="OpenTracksCardView">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="cardElevation">1dp</item>
@@ -336,7 +76,7 @@ limitations under the License.
<!-- Snackbar -->
<style name="MaterialSnackbarTheme" parent="@style/Widget.MaterialComponents.Snackbar">
<item name="materialThemeOverlay">@style/ThemeOverlay.App.Snackbar</item>
<item name="materialThemeOverlay">@style/ThemeOverlay.OpenTracks.Snackbar</item>
<item name="actionTextColorAlpha">1</item>
<item name="backgroundTint">@android:color/holo_red_dark</item>
@@ -353,7 +93,7 @@ limitations under the License.
<item name="android:textSize">14sp</item>
</style>
<style name="ThemeOverlay.App.Snackbar" parent="">
<style name="ThemeOverlay.OpenTracks.Snackbar" parent="">
<item name="colorSurface">@android:color/holo_red_dark</item>
<item name="colorOnSurface">@android:color/holo_red_dark</item>
</style>
+26 -5
View File
@@ -15,20 +15,41 @@ limitations under the License.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="ThemeCustom" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- <item name="colorPrimary">TODO</item>-->
<!-- <item name="colorPrimaryDark">TODO</item>-->
<style name="ThemeCustom" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/opentracks_primary_color</item>
<item name="colorPrimaryVariant">@color/opentracks_primary_variant_color</item>
<item name="colorOnPrimary">@color/opentracks_on_primary_color</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:activatedBackgroundIndicator">@drawable/activated_background</item>
<item name="colorSecondary">@color/opentracks_secondary_color</item>
<item name="colorSecondaryVariant">@color/opentracks_secondary_color</item>
<item name="colorOnSecondary">@color/opentracks_on_secondary_color</item>
<item name="colorError">@color/red_dark</item>
<item name="colorOnError">@android:color/white</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<item name="windowActionModeOverlay">true</item>
<item name="android:colorBackground">@android:color/white</item>
<item name="colorOnBackground">@android:color/black</item>
<item name="colorSurface">@color/opentracks_surface_day_background_color</item>
<item name="colorOnSurface">@color/opentracks_surface_day_on_background_color</item>
<item name="android:activatedBackgroundIndicator">@drawable/activated_background</item>
<!-- Snackbar -->
<item name="snackbarStyle">@style/MaterialSnackbarTheme</item>
<item name="snackbarButtonStyle">@style/MaterialSnackbarTextButtonTheme</item>
<item name="snackbarTextViewStyle">@style/MaterialSnackbarTextViewTheme</item>
<!-- Linear progress indicator -->
<item name="linearProgressIndicatorStyle">@style/OpenTracksLinearProgressIndicator</item>
</style>
<style name="OpenTracksLinearProgressIndicator" parent="Widget.MaterialComponents.LinearProgressIndicator">
<item name="indicatorColor">?attr/colorSecondary</item>
</style>
<style name="SplashTheme" parent="ThemeCustom">
+55
View File
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="TextAppearance.OpenTracks.PrimaryHeader" parent="TextAppearance.MaterialComponents.Subtitle1">
<item name="android:textColor">?attr/colorSecondary</item>
<item name="android:layout_marginTop">8dp</item>
<item name="android:textStyle">bold</item>
<item name="android:textAllCaps">true</item>
<item name="android:singleLine">true</item>
</style>
<style name="TextAppearance.OpenTracks.SecondaryHeader" parent="TextAppearance.MaterialComponents.Subtitle2">
<item name="android:layout_marginTop">8dp</item>
<item name="android:textAllCaps">true</item>
<item name="android:singleLine">true</item>
</style>
<style name="TextAppearance.OpenTracks.HiddenHeader" parent="TextAppearance.MaterialComponents.Subtitle1">
<item name="android:textColor">@color/light_gray</item>
<item name="android:layout_marginTop">8dp</item>
<item name="android:textAllCaps">true</item>
<item name="android:singleLine">true</item>
</style>
<style name="TextAppearance.OpenTracks.PrimaryValue" parent="TextAppearance.MaterialComponents.Headline4">
<item name="android:textColor">?attr/colorOnBackground</item>
<item name="android:layout_gravity">center</item>
<item name="android:textStyle">bold</item>
<item name="android:singleLine">true</item>
<item name="android:layout_marginTop">8dp</item>
<item name="android:layout_marginBottom">8dp</item>
</style>
<style name="TextAppearance.OpenTracks.SecondaryValue" parent="TextAppearance.MaterialComponents.Headline5">
<item name="android:textColor">?attr/colorOnBackground</item>
<item name="android:layout_gravity">center</item>
<item name="android:textStyle">bold</item>
<item name="android:singleLine">true</item>
<item name="android:layout_marginTop">8dp</item>
<item name="android:layout_marginBottom">8dp</item>
</style>
<style name="TextAppearance.OpenTracks.HiddenValue" parent="TextAppearance.MaterialComponents.Headline4">
<item name="android:textColor">@color/light_gray</item>
<item name="android:layout_gravity">center</item>
<item name="android:textStyle">bold</item>
<item name="android:singleLine">true</item>
<item name="android:layout_marginTop">8dp</item>
<item name="android:layout_marginBottom">8dp</item>
</style>
<style name="TextAppearance.OpenTracks.PrimaryUnit" parent="TextAppearance.MaterialComponents.Caption">
<item name="android:textColor">?attr/colorOnBackground</item>
<item name="android:singleLine">true</item>
</style>
</resources>
@@ -26,4 +26,9 @@
android:title="@string/menu_voice_rate"
app:useSimpleSummaryProvider="true" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/voice_announce_heart_rate_key"
android:title="@string/settings_announcements_report_heart_rate" />
</PreferenceScreen>