mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-23 18:55:17 +02:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cdf122e8c | |||
| 495ef2fc6f | |||
| ff561c1b25 | |||
| 7106f64a3c | |||
| 8420f9febb | |||
| 0744ba790f | |||
| 9a7f24bddc | |||
| ef47c86e7c | |||
| 93f203bcf1 | |||
| 6b0ec72b9b | |||
| d46cea6150 | |||
| 0e3ffa58f0 | |||
| c00d769188 | |||
| 00489a121a | |||
| d7f339c6f8 | |||
| da28ea3e95 | |||
| 9872658f81 | |||
| b6b155f19c | |||
| 067f31ac21 | |||
| 3f0ce27870 | |||
| 438f8fe273 | |||
| 392c02a56f | |||
| 6dca3bea58 | |||
| 13034c8bb3 | |||
| 1175ce270e | |||
| 02cad98a4f | |||
| d6b38a390e | |||
| 98deed8ef7 | |||
| 9983168cb9 | |||
| 7b5f29db09 | |||
| 49efd04326 | |||
| 3159c62eb2 | |||
| 0e8811d8c9 | |||
| 4126e36f1c | |||
| b7e2df9f2f | |||
| 055093772d | |||
| 7f8b49814b | |||
| 5cc0e06fbb | |||
| 311a2550e5 | |||
| 4311b29833 | |||
| 86121ac27a | |||
| ed6daeb48b | |||
| af72960cf6 | |||
| 99767d0c0b | |||
| 1fc0a552c7 | |||
| 18dd1de1f9 |
@@ -15,7 +15,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2.3.4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ jobs:
|
|||||||
target: [default]
|
target: [default]
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2.3.4
|
||||||
|
|
||||||
- name: run tests
|
- name: run tests
|
||||||
uses: reactivecircus/android-emulator-runner@v2
|
uses: reactivecircus/android-emulator-runner@v2.16.0
|
||||||
with:
|
with:
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<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" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
Generated
+1
@@ -3,6 +3,7 @@
|
|||||||
<component name="RunConfigurationProducerService">
|
<component name="RunConfigurationProducerService">
|
||||||
<option name="ignoredProducers">
|
<option name="ignoredProducers">
|
||||||
<set>
|
<set>
|
||||||
|
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||||
|
|||||||
@@ -49,39 +49,6 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## Features:
|
|
||||||
* __Tracking:__ track your sport and outdoor activities
|
|
||||||
* __Photos and Markers:__ mark interesting locations while tracking (also with photos)
|
|
||||||
* __Bluetooth LE sensors:__
|
|
||||||
* heart rate
|
|
||||||
* cycling: speed and cadence
|
|
||||||
* cycling: power meter
|
|
||||||
* __Export data:__ export tracks either as KMZ (incl. photos), KML or GPX
|
|
||||||
* __No Internet access:__ Internet is not used
|
|
||||||
* __Voice announcements__
|
|
||||||
* __No advertising__
|
|
||||||
* __Privacy:__
|
|
||||||
* _does not contain any in-app analytics_
|
|
||||||
* _does not use Google Play Services_
|
|
||||||
* _has no cloud integration_
|
|
||||||
|
|
||||||
__Only required permissions:__
|
|
||||||
* _ACCESS_FINE_LOCATION_: required to use the GPS.
|
|
||||||
|
|
||||||
An overview of Bluetooth LE sensors that are known to work with OpenTracks is in [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md).
|
|
||||||
|
|
||||||
_Please note:_
|
|
||||||
_OpenTracks_ does not contain a _map_ (as this would require internet access).
|
|
||||||
|
|
||||||
## Custom Dashboards (incl. Map)
|
|
||||||
As of v3.3.1 OpenTracks enables to use custom dashboards.
|
|
||||||
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which presents an OpenStreetMap map (showing the current track, incl. updates).
|
|
||||||
The developer documentation is in [README_API.md](README_API.md).
|
|
||||||
|
|
||||||
Alternatively, recorded tracks can be shared as KMZ/GPX to installed applications (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand)).
|
|
||||||
However, this is rather slow and does not provide updates while recording.
|
|
||||||
|
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
<div>
|
<div>
|
||||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot1.png">
|
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot1.png">
|
||||||
@@ -96,12 +63,54 @@ However, this is rather slow and does not provide updates while recording.
|
|||||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot8.png">
|
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot8.png">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
## Features
|
||||||
|
* __Tracking:__ track your sport and outdoor activities
|
||||||
|
* __Voice announcements__
|
||||||
|
* __Photos and Markers:__ mark interesting locations while tracking
|
||||||
|
* __Export:__
|
||||||
|
* export tracks either as KMZ (incl. photos), KML, or GPX
|
||||||
|
* export automatically after each recording (e.g., to sync)
|
||||||
|
* avoid duplication: each exported file contain a random unique identifier (i.e., `opentracks:trackid`)
|
||||||
|
* __Altitude:__
|
||||||
|
* gain/loss via barometric sensor (if present)
|
||||||
|
* shown in EGM2008 (above mean sea level); exported as WGS84
|
||||||
|
* __Bluetooth LE sensors:__
|
||||||
|
* heart rate
|
||||||
|
* cycling: speed and distance
|
||||||
|
* cycling: cadence
|
||||||
|
* cycling: power meter
|
||||||
|
|
||||||
|
### Gadgetbridge integration
|
||||||
|
|
||||||
|
OpenTracks can be used with [Gadgetbridge](https://www.gadgetbridge.org/):
|
||||||
|
* shows statistics via notification on smart watches (requires Gadgetbridge 0.56.1 or later), and
|
||||||
|
* Gadgetbridge's GPX exporter generates `opentracks:trackid` to avoid duplication (Gadgetbridge 0.53.0 or later).
|
||||||
|
|
||||||
|
### Privacy
|
||||||
|
* __No Internet access:__ Internet is not used
|
||||||
|
* __No advertising__
|
||||||
|
* __No in-app analytics__
|
||||||
|
* __No use of Google Play Services__
|
||||||
|
|
||||||
|
__Only required permission:__
|
||||||
|
* _ACCESS_FINE_LOCATION_: required to use the GPS.
|
||||||
|
|
||||||
|
An overview of Bluetooth LE sensors that are known to work with OpenTracks is in [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md).
|
||||||
|
|
||||||
|
## Custom Dashboards (incl. map)
|
||||||
|
As of v3.3.1, OpenTracks supports custom dashboards.
|
||||||
|
The reference implementation is [OSMDashboard](https://github.com/OpenTracksApp/OSMDashboard), which presents an OpenStreetMap map (showing the current track, incl. updates).
|
||||||
|
The developer documentation is in [README_API.md](README_API.md).
|
||||||
|
|
||||||
|
Alternatively, recorded tracks can be shared as KMZ/GPX with installed applications (e.g., [OsmAnd](https://play.google.com/store/apps/details?id=net.osmand)).
|
||||||
|
However, this is rather slow and does not provide updates while recording.
|
||||||
|
|
||||||
## Project history
|
## Project history
|
||||||
|
|
||||||
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
|
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
|
||||||
Initially, _My Tracks_ was initially released by Google in 2010 as [open-source software](http://google-latlong.blogspot.fr/2010/05/code-for-my-tracks-is-now-yours.html).
|
Initially, _My Tracks_ was initially released by Google in 2010 as [open-source software](http://google-latlong.blogspot.fr/2010/05/code-for-my-tracks-is-now-yours.html).
|
||||||
In 2016, [Google decided to discontinue](https://support.google.com/maps/answer/6333516) _My Tracks_ and stopped distributing it via the Google Play store in April 2016.
|
In 2016, [Google decided to discontinue](https://support.google.com/maps/answer/6333516) _My Tracks_ and stopped distributing it via the Google Play store in April 2016.
|
||||||
The [Plonk42](https://github.com/plonk42) conducted some maintenance work until 2016, so _My Tracks_ could still be used (based upon version _Google's MyTracks_ version 2.0.6).
|
Then [Plonk42](https://github.com/plonk42) conducted some maintenance work until 2016, so _My Tracks_ could still be used (based upon version _Google's MyTracks_ version 2.0.6).
|
||||||
Plonk42's version is available [here](https://github.com/Plonk42/mytracks).
|
Plonk42's version is available [here](https://github.com/Plonk42/mytracks).
|
||||||
In 2019, _OpenTracks_ was forked from Plonk42's _My Tracks_ and major rework was conducted.
|
In 2019, _OpenTracks_ was forked from Plonk42's _My Tracks_ and major rework was conducted.
|
||||||
|
|
||||||
@@ -114,11 +123,11 @@ Rework of _OpenTracks_ included:
|
|||||||
* removing calorie estimation and activity estimation,
|
* removing calorie estimation and activity estimation,
|
||||||
* removing support for ANT+ and Classic Bluetooth,
|
* removing support for ANT+ and Classic Bluetooth,
|
||||||
* adding support for Bluetooth LE heart rate sensors,
|
* adding support for Bluetooth LE heart rate sensors,
|
||||||
* removing Protobuf (store sensosensorr data in SQLite columns directly), and
|
* removing Protobuf (store sensor data in SQLite columns directly), and
|
||||||
* removing Android Service API for other apps.
|
* removing Android Service API for other apps.
|
||||||
|
|
||||||
Artwork, logos and user interface remained more or less unchanged.
|
Artwork, logos and user interface remained more or less unchanged.
|
||||||
|
|
||||||
More information about _Google My Tracks_:
|
More information about _Google My Tracks_:
|
||||||
* [Wikipedia page](https://en.wikipedia.org/wiki/MyTracks)
|
* [Wikipedia page](https://en.wikipedia.org/wiki/MyTracks)
|
||||||
* [Google code archive](https://code.google.com/archive/p/mytracks/)
|
* [Google code archive](https://code.google.com/archive/p/mytracks/)
|
||||||
+5
-5
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.2.0'
|
classpath 'com.android.tools.build:gradle:4.2.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,8 +55,8 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "de.dennisguse.opentracks"
|
applicationId "de.dennisguse.opentracks"
|
||||||
versionCode 4046
|
versionCode 4092
|
||||||
versionName "v3.18.0"
|
versionName "v3.18.1"
|
||||||
|
|
||||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ dependencies {
|
|||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha06'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha06'
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||||
implementation 'androidx.preference:preference:1.1.1'
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation 'com.google.android.material:material:1.3.0'
|
implementation 'com.google.android.material:material:1.3.0'
|
||||||
@@ -103,5 +103,5 @@ dependencies {
|
|||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||||
androidTestImplementation 'androidx.test:rules:1.3.0'
|
androidTestImplementation 'androidx.test:rules:1.3.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||||
androidTestImplementation 'org.mockito:mockito-android:3.9.0'
|
androidTestImplementation 'org.mockito:mockito-android:3.10.0'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
v3.18.1: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Deleting tracks is handled by a service
|
||||||
|
- Uninstall deletes all data (i.e., hasFragileData=false)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- Race condition: crash while recording and switching OpenTracks into background
|
||||||
|
- Search in track list was not working
|
||||||
|
- Cycling speed sensor: distance computation was wrong (only affected statistics)
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- ControllerFragment replaces TrackController
|
||||||
@@ -3,9 +3,9 @@ OpenTracks is a sport tracking application that completely respects your privacy
|
|||||||
Features:
|
Features:
|
||||||
* Sport tracking: track your sport and outdoor activities
|
* Sport tracking: track your sport and outdoor activities
|
||||||
* Photos and markers: mark interesting locations while tracking
|
* Photos and markers: mark interesting locations while tracking
|
||||||
* Supports Bluetooth LE sensors:
|
* Altitude gain and loss: via barometric sensor
|
||||||
* heart rate
|
* Altitude shown in EGM2008 (above mean sea level); exported as WGS84
|
||||||
* cycling: speed and cadence
|
* Voice announcements
|
||||||
* cycling: power meter
|
* Supports Bluetooth LE sensors: heart rate, speed and distance (cycling), cadence (cycling), and power meter (cycling)
|
||||||
* Export data: export tracks either as KML or GPX
|
* Export data: export tracks either as KMZ (incl. photos), KML, or GPX
|
||||||
* No Internet access: Internet is not used
|
* No Internet access: Internet is not used
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.app.Instrumentation;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
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.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.filters.LargeTest;
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import androidx.test.rule.GrantPermissionRule;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.hamcrest.Description;
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
import org.hamcrest.TypeSafeMatcher;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.services.TrackDeleteService;
|
||||||
|
|
||||||
|
import static androidx.test.espresso.Espresso.onData;
|
||||||
|
import static androidx.test.espresso.Espresso.onView;
|
||||||
|
import static androidx.test.espresso.Espresso.pressBack;
|
||||||
|
import static androidx.test.espresso.action.ViewActions.click;
|
||||||
|
import static androidx.test.espresso.action.ViewActions.scrollTo;
|
||||||
|
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||||
|
import static org.hamcrest.Matchers.allOf;
|
||||||
|
import static org.hamcrest.Matchers.anything;
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class EspressoDeleteTrackTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant("android.permission.ACCESS_FINE_LOCATION");
|
||||||
|
|
||||||
|
private MyIdlingResource idlingResource;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void registerIntentServiceIdlingResource() {
|
||||||
|
Instrumentation instrumentation
|
||||||
|
= InstrumentationRegistry.getInstrumentation();
|
||||||
|
idlingResource = new MyIdlingResource(instrumentation.getTargetContext());
|
||||||
|
Espresso.registerIdlingResources(idlingResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void unregisterIntentServiceIdlingResource() {
|
||||||
|
Espresso.unregisterIdlingResources(idlingResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void espressoDeleteTrackTest() {
|
||||||
|
int countBefore;
|
||||||
|
|
||||||
|
{
|
||||||
|
// TrackListActivity: start recording
|
||||||
|
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||||
|
trackControllerRecordButton.perform(click());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// TrackRecordingActivity
|
||||||
|
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||||
|
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||||
|
|
||||||
|
// wait; stay recording
|
||||||
|
trackControllerRecordButton.perform(waitFor(5000));
|
||||||
|
|
||||||
|
// stop;
|
||||||
|
trackControllerStopButton.perform(veryLongTouch(1600));
|
||||||
|
}
|
||||||
|
|
||||||
|
// back
|
||||||
|
pressBack();
|
||||||
|
|
||||||
|
// get number of items before deleting a track
|
||||||
|
countBefore = numberOfItemsListView();
|
||||||
|
|
||||||
|
// select track
|
||||||
|
onData(anything()).inAdapterView(withId(R.id.track_list)).atPosition(0).perform(veryLongTouch(5000));
|
||||||
|
|
||||||
|
// open menu and delete selected track
|
||||||
|
ViewInteraction overflowMenuButton = onView(
|
||||||
|
allOf(withContentDescription("More options"),
|
||||||
|
childAtPosition(
|
||||||
|
childAtPosition(
|
||||||
|
withId(R.id.toolbar),
|
||||||
|
2),
|
||||||
|
2),
|
||||||
|
isDisplayed()));
|
||||||
|
overflowMenuButton.perform(click());
|
||||||
|
|
||||||
|
ViewInteraction appCompatTextView = onView(
|
||||||
|
allOf(withId(R.id.title), withText("Delete"),
|
||||||
|
childAtPosition(
|
||||||
|
childAtPosition(
|
||||||
|
withId(R.id.content),
|
||||||
|
0),
|
||||||
|
0),
|
||||||
|
isDisplayed()));
|
||||||
|
appCompatTextView.perform(click());
|
||||||
|
|
||||||
|
ViewInteraction appCompatButton = onView(
|
||||||
|
allOf(withId(android.R.id.button1), withText("Yes"),
|
||||||
|
childAtPosition(
|
||||||
|
childAtPosition(
|
||||||
|
withId(R.id.buttonPanel),
|
||||||
|
0),
|
||||||
|
3)));
|
||||||
|
appCompatButton.perform(scrollTo(), click());
|
||||||
|
|
||||||
|
// check number of items after delete
|
||||||
|
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<View>() {
|
||||||
|
@Override
|
||||||
|
public void describeTo(Description description) {
|
||||||
|
description.appendText("Child at position " + position + " in parent ");
|
||||||
|
parentMatcher.describeTo(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matchesSafely(View view) {
|
||||||
|
ViewParent parent = view.getParent();
|
||||||
|
return parent instanceof ViewGroup && parentMatcher.matches(parent)
|
||||||
|
&& view.equals(((ViewGroup) parent).getChildAt(position));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matcher<View> withListSize (final int size) {
|
||||||
|
return new TypeSafeMatcher<View> () {
|
||||||
|
@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];
|
||||||
|
onView(withId(R.id.track_list)).check(matches(new TypeSafeMatcher<View>() {
|
||||||
|
@Override
|
||||||
|
public boolean matchesSafely(View view) {
|
||||||
|
ListView listView = (ListView) view;
|
||||||
|
|
||||||
|
counts[0] = listView.getCount();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void describeTo(Description description) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return counts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class MyIdlingResource implements IdlingResource {
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
private ResourceCallback resourceCallback;
|
||||||
|
|
||||||
|
public MyIdlingResource(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return MyIdlingResource.class.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerIdleTransitionCallback(IdlingResource.ResourceCallback resourceCallback) {
|
||||||
|
this.resourceCallback = resourceCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isIdleNow() {
|
||||||
|
boolean idle = !isDeleteServiceRunning();
|
||||||
|
if (idle && resourceCallback != null) {
|
||||||
|
resourceCallback.onTransitionToIdle();
|
||||||
|
}
|
||||||
|
return idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDeleteServiceRunning() {
|
||||||
|
ActivityManager manager =
|
||||||
|
(ActivityManager) context.getSystemService(
|
||||||
|
Context.ACTIVITY_SERVICE);
|
||||||
|
for (ActivityManager.RunningServiceInfo info :
|
||||||
|
manager.getRunningServices(Integer.MAX_VALUE)) {
|
||||||
|
if (TrackDeleteService.class.getName().equals(
|
||||||
|
info.service.getClassName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,10 +19,13 @@ import org.junit.Rule;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static androidx.test.espresso.Espresso.onData;
|
||||||
import static androidx.test.espresso.Espresso.onView;
|
import static androidx.test.espresso.Espresso.onView;
|
||||||
import static androidx.test.espresso.action.ViewActions.click;
|
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.isDisplayed;
|
||||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static org.hamcrest.Matchers.anything;
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@@ -39,13 +42,13 @@ public class EspressoUITest {
|
|||||||
public void record_pause_resume_stop() {
|
public void record_pause_resume_stop() {
|
||||||
{
|
{
|
||||||
// TrackListActivity: start recording
|
// TrackListActivity: start recording
|
||||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
|
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||||
trackControllerRecordButton.perform(click());
|
trackControllerRecordButton.perform(click());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// TrackRecordingActivity
|
// TrackRecordingActivity
|
||||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
|
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_controller_stop));
|
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||||
|
|
||||||
// wait; stay recording
|
// wait; stay recording
|
||||||
trackControllerRecordButton.perform(waitFor(5000));
|
trackControllerRecordButton.perform(waitFor(5000));
|
||||||
@@ -72,13 +75,13 @@ public class EspressoUITest {
|
|||||||
public void record_move_through_tabs() {
|
public void record_move_through_tabs() {
|
||||||
{
|
{
|
||||||
// TrackListActivity: start recording
|
// TrackListActivity: start recording
|
||||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
|
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||||
trackControllerRecordButton.perform(click());
|
trackControllerRecordButton.perform(click());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// TrackRecordingActivity
|
// TrackRecordingActivity
|
||||||
ViewInteraction tabLayout = onView(withId(R.id.track_detail_activity_tablayout));
|
ViewInteraction tabLayout = onView(withId(R.id.track_detail_activity_tablayout));
|
||||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_controller_stop));
|
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||||
|
|
||||||
tabLayout.perform(selectTabAtIndex(1));
|
tabLayout.perform(selectTabAtIndex(1));
|
||||||
tabLayout.perform(waitFor(1000));
|
tabLayout.perform(waitFor(1000));
|
||||||
@@ -97,6 +100,13 @@ public class EspressoUITest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
|
@Test
|
||||||
|
public void selectAndDeleteTrack() {
|
||||||
|
onView(withId(R.id.track_list)).check(matches(isDisplayed()));
|
||||||
|
onData(anything()).inAdapterView(withId(R.id.track_list)).atPosition(0).perform(veryLongTouch(2000));
|
||||||
|
}
|
||||||
|
|
||||||
private static ViewAction veryLongTouch(final int duration_ms) {
|
private static ViewAction veryLongTouch(final int duration_ms) {
|
||||||
return new ViewAction() {
|
return new ViewAction() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package de.dennisguse.opentracks.content.data;
|
package de.dennisguse.opentracks.content.data;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
@@ -25,4 +28,35 @@ public class TrackPointTest {
|
|||||||
|
|
||||||
assertFalse(tp.isRecent());
|
assertFalse(tp.isRecent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void distanceToPrevious() {
|
||||||
|
Location l1 = new Location("test");
|
||||||
|
l1.setLatitude(0);
|
||||||
|
l1.setLongitude(0.0001);
|
||||||
|
TrackPoint tp1 = new TrackPoint(l1);
|
||||||
|
|
||||||
|
Location l2 = new Location("test");
|
||||||
|
l2.setLatitude(0);
|
||||||
|
l2.setLongitude(0.0002);
|
||||||
|
TrackPoint tp2 = new TrackPoint(l2);
|
||||||
|
|
||||||
|
// without sensor distance
|
||||||
|
assertEquals(11.13, tp2.distanceToPrevious(tp1).toM(), 0.01);
|
||||||
|
|
||||||
|
// tp1 has sensor distance
|
||||||
|
tp1.setSensorDistance(Distance.of(5));
|
||||||
|
tp1.setSensorDistance(null);
|
||||||
|
assertEquals(11.13, tp2.distanceToPrevious(tp1).toM(), 0.01);
|
||||||
|
|
||||||
|
// tp2 has sensor distance
|
||||||
|
tp1.setSensorDistance(null);
|
||||||
|
tp2.setSensorDistance(Distance.of(5));
|
||||||
|
assertEquals(5, tp2.distanceToPrevious(tp1).toM(), 0.01);
|
||||||
|
|
||||||
|
// tp1 and tp2 have sensor distance
|
||||||
|
tp1.setSensorDistance(Distance.of(10));
|
||||||
|
tp2.setSensorDistance(Distance.of(5));
|
||||||
|
assertEquals(5, tp2.distanceToPrevious(tp1).toM(), 0.01);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+3
-2
@@ -5,6 +5,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.util.UintUtils;
|
import de.dennisguse.opentracks.util.UintUtils;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -100,7 +101,7 @@ public class SensorDataCyclingTest {
|
|||||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 2, 8016);
|
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 2, 8016);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.compute(previous, 2150);
|
current.compute(previous, Distance.ofMM(2150));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(2.15, current.getValue().distance.toM(), 0.01);
|
assertEquals(2.15, current.getValue().distance.toM(), 0.01);
|
||||||
@@ -114,7 +115,7 @@ public class SensorDataCyclingTest {
|
|||||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
current.compute(previous, 2000);
|
current.compute(previous, Distance.ofMM(2000));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(2, current.getValue().distance.toM(), 0.01);
|
assertEquals(2, current.getValue().distance.toM(), 0.01);
|
||||||
|
|||||||
@@ -146,6 +146,34 @@ public class TrackStatisticsUpdaterTest {
|
|||||||
assertEquals(15, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
|
assertEquals(15, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addTrackPoint_distance_from_GPS_moving_and_sensor_disconnecting() {
|
||||||
|
// given
|
||||||
|
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||||
|
|
||||||
|
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||||
|
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
||||||
|
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
||||||
|
tp3.setSensorDistance(Distance.of(5f));
|
||||||
|
TrackPoint tp4 = new TrackPoint(0.0005, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
|
||||||
|
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||||
|
|
||||||
|
// when
|
||||||
|
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||||
|
subject.addTrackPoint(tp2, GPS_DISTANCE);
|
||||||
|
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(5, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
|
||||||
|
|
||||||
|
// when
|
||||||
|
subject.addTrackPoint(tp4, GPS_DISTANCE);
|
||||||
|
subject.addTrackPoint(tp5, GPS_DISTANCE);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(59.18, subject.getTrackStatistics().getTotalDistance().toM(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
|
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
|
||||||
@Test
|
@Test
|
||||||
public void addTrackPoint_speed_from_GPS_not_moving() {
|
public void addTrackPoint_speed_from_GPS_not_moving() {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:hasFragileUserData="true"
|
||||||
|
tools:replace="android:hasFragileUserData" />
|
||||||
|
</manifest>
|
||||||
@@ -51,7 +51,7 @@ limitations under the License.
|
|||||||
android:name=".Startup"
|
android:name=".Startup"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:hasFragileUserData="true"
|
android:hasFragileUserData="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
@@ -59,10 +59,6 @@ limitations under the License.
|
|||||||
|
|
||||||
<activity android:name=".AboutActivity" />
|
<activity android:name=".AboutActivity" />
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".TrackDeleteActivity"
|
|
||||||
android:theme="@style/TrackDeleteActivity" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".HelpActivity"
|
android:name=".HelpActivity"
|
||||||
android:label="@string/menu_help" />
|
android:label="@string/menu_help" />
|
||||||
@@ -350,6 +346,13 @@ limitations under the License.
|
|||||||
android:name=".io.file.importer.ImportService"
|
android:name=".io.file.importer.ImportService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".services.TrackDeleteService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false"
|
||||||
|
android:icon="@drawable/ic_logo_color_24dp"
|
||||||
|
android:label="@string/delete_service" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
|
|||||||
+40
-20
@@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -26,33 +24,38 @@ import java.util.Arrays;
|
|||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller;
|
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller;
|
||||||
|
import de.dennisguse.opentracks.services.TrackDeleteService;
|
||||||
|
import de.dennisguse.opentracks.services.TrackDeleteServiceConnection;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract class for the following common tasks across
|
* An abstract class for the following common tasks across
|
||||||
* {@link TrackListActivity} and {@link TrackRecordedActivity}:
|
* {@link TrackListActivity} and {@link TrackRecordedActivity}:
|
||||||
* <p>
|
* <p>
|
||||||
* - share track <br>
|
|
||||||
* - delete tracks <br>
|
* - delete tracks <br>
|
||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
//TODO It is actually not a List; we need a better name for this class
|
|
||||||
//TODO Check if this class is still such a good idea; inheritance might limit maintainability
|
//TODO Check if this class is still such a good idea; inheritance might limit maintainability
|
||||||
public abstract class AbstractListActivity extends AbstractActivity implements ConfirmDeleteCaller {
|
public abstract class AbstractTrackDeleteActivity extends AbstractActivity implements ConfirmDeleteCaller, TrackDeleteServiceConnection.Listener {
|
||||||
|
|
||||||
private static final String TAG = AbstractListActivity.class.getSimpleName();
|
private static final String TAG = AbstractTrackDeleteActivity.class.getSimpleName();
|
||||||
|
|
||||||
protected static final int GPS_REQUEST_CODE = 6;
|
private TrackDeleteServiceConnection trackDeleteServiceConnection;
|
||||||
private static final int DELETE_REQUEST_CODE = 3;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onStart() {
|
||||||
if (requestCode == DELETE_REQUEST_CODE) {
|
super.onStart();
|
||||||
onTrackDeleted();
|
trackDeleteServiceConnection = new TrackDeleteServiceConnection(this);
|
||||||
} else {
|
trackDeleteServiceConnection.bind(this);
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
if (trackDeleteServiceConnection != null) {
|
||||||
|
trackDeleteServiceConnection.unbind(this);
|
||||||
|
trackDeleteServiceConnection = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +72,8 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
|
|||||||
public void onConfirmDeleteDone(Track.Id... trackIds) {
|
public void onConfirmDeleteDone(Track.Id... trackIds) {
|
||||||
boolean stopRecording = false;
|
boolean stopRecording = false;
|
||||||
|
|
||||||
|
onDeleteConfirmed();
|
||||||
|
|
||||||
for (Track.Id trackId : trackIds) {
|
for (Track.Id trackId : trackIds) {
|
||||||
if (trackId.equals(getRecordingTrackId())) {
|
if (trackId.equals(getRecordingTrackId())) {
|
||||||
stopRecording = true;
|
stopRecording = true;
|
||||||
@@ -79,10 +84,9 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
|
|||||||
if (stopRecording) {
|
if (stopRecording) {
|
||||||
getTrackRecordingServiceConnection().stopRecording(this);
|
getTrackRecordingServiceConnection().stopRecording(this);
|
||||||
}
|
}
|
||||||
Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class);
|
|
||||||
intent.putParcelableArrayListExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, new ArrayList<>(Arrays.asList(trackIds)));
|
trackDeleteServiceConnection = new TrackDeleteServiceConnection(this);
|
||||||
startActivityForResult(intent, DELETE_REQUEST_CODE);
|
trackDeleteServiceConnection.startAndBind(this, new ArrayList<>(Arrays.asList(trackIds)));
|
||||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,12 +98,28 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after {@link TrackDeleteActivity} returns its result.
|
* Called every time a track is deleted.
|
||||||
*/
|
*/
|
||||||
abstract protected void onTrackDeleted();
|
protected void onTrackDeleteStatus(TrackDeleteService.DeleteStatus deleteStatus) {
|
||||||
|
if (deleteStatus.isFinished() && trackDeleteServiceConnection != null) {
|
||||||
|
trackDeleteServiceConnection.unbind(this);
|
||||||
|
trackDeleteServiceConnection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void onDeleteConfirmed();
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
protected Track.Id getRecordingTrackId() {
|
protected Track.Id getRecordingTrackId() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connected() {
|
||||||
|
TrackDeleteService service = trackDeleteServiceConnection.getServiceIfBound();
|
||||||
|
if (service == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
service.getDeletingStatusObservable().observe(AbstractTrackDeleteActivity.this, status -> onTrackDeleteStatus(status));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 Google Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.graphics.drawable.TransitionDrawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.databinding.ControllerFragmentBinding;
|
||||||
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
|
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||||
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track controller for record, pause, resume, and stop.
|
||||||
|
*
|
||||||
|
* @author Jimmy Shih
|
||||||
|
*/
|
||||||
|
//TODO Move buttons into separate components
|
||||||
|
public class ControllerFragment extends Fragment implements View.OnTouchListener {
|
||||||
|
|
||||||
|
private static final String TAG = ControllerFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
//TODO TrackRecordingService should provide LifeData for time (replace internal timer)
|
||||||
|
private static final Duration UI_UPDATE_INTERVAL = Duration.ofSeconds(1);
|
||||||
|
@Deprecated
|
||||||
|
private Handler handlerUpdateTotalTime;
|
||||||
|
|
||||||
|
private Runnable buttonDelay;
|
||||||
|
private int buttonDelayDuration;
|
||||||
|
|
||||||
|
private ControllerFragmentBinding viewBinding;
|
||||||
|
private TransitionDrawable transition;
|
||||||
|
|
||||||
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
|
private TrackRecordingService.RecordingStatus recordingStatus;
|
||||||
|
private Duration totalTime;
|
||||||
|
|
||||||
|
private final Runnable bindChangedCallback = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||||
|
if (service == null) {
|
||||||
|
Log.w(TAG, "could not get TrackRecordingService");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
service.getRecordingStatusObservable()
|
||||||
|
.observe(ControllerFragment.this, status -> onRecordingStatusChanged(status));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// A runnable to update the total time.
|
||||||
|
@Deprecated
|
||||||
|
private final Runnable updateTotalTimeRunnable = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (isResumed() && recordingStatus.isRecordingAndNotPaused()) {
|
||||||
|
updateTotalTime();
|
||||||
|
setTotalTime();
|
||||||
|
handlerUpdateTotalTime.postDelayed(this, UI_UPDATE_INTERVAL.toMillis());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
buttonDelayDuration = getResources().getInteger(R.integer.buttonDelayMillis);
|
||||||
|
|
||||||
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||||
|
handlerUpdateTotalTime = new Handler();
|
||||||
|
recordingStatus = TrackRecordingService.STATUS_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
viewBinding = ControllerFragmentBinding.inflate(inflater, container, false);
|
||||||
|
|
||||||
|
viewBinding.controllerRecord.setOnTouchListener(this);
|
||||||
|
viewBinding.controllerRecord.setOnClickListener((view) -> {
|
||||||
|
if (buttonDelay != null || recordingStatus.isRecordingAndNotPaused()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCallback().recordStart();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewBinding.controllerStop.setOnTouchListener(this);
|
||||||
|
transition = (TransitionDrawable) viewBinding.controllerContainer.getBackground();
|
||||||
|
|
||||||
|
onRecordingStatusChanged(recordingStatus);
|
||||||
|
|
||||||
|
return viewBinding.getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
trackRecordingServiceConnection.startConnection(getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
trackRecordingServiceConnection.unbind(getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
viewBinding = null;
|
||||||
|
transition = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
trackRecordingServiceConnection = null;
|
||||||
|
handlerUpdateTotalTime = null;
|
||||||
|
recordingStatus = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO Move into a separate object
|
||||||
|
@SuppressLint("ClickableViewAccessibility") //We do accessibility manually.
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(final View view, MotionEvent event) {
|
||||||
|
if (!isResumed()) {
|
||||||
|
Log.w(TAG, "The UI is already gone, no need to update anything; seen on Android 7.1.2");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonDelay != null && event.getAction() == MotionEvent.ACTION_UP) {
|
||||||
|
handlerUpdateTotalTime.removeCallbacks(buttonDelay);
|
||||||
|
buttonDelay = null;
|
||||||
|
view.setPressed(false);
|
||||||
|
|
||||||
|
transition.resetTransition();
|
||||||
|
|
||||||
|
showStatusSetDefaultText();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Trigger button pressed animation
|
||||||
|
view.setPressed(true);
|
||||||
|
|
||||||
|
//To pause a recording
|
||||||
|
if (viewBinding.controllerRecord.equals(view)
|
||||||
|
&& event.getAction() == MotionEvent.ACTION_DOWN
|
||||||
|
&& recordingStatus.isRecordingAndNotPaused()) {
|
||||||
|
|
||||||
|
transition.startTransition(buttonDelayDuration);
|
||||||
|
|
||||||
|
ActivityUtils.vibrate(getContext(), 150);
|
||||||
|
viewBinding.controllerStatus.setText(R.string.hold_to_pause);
|
||||||
|
|
||||||
|
buttonDelay = () -> {
|
||||||
|
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||||
|
view.performClick();
|
||||||
|
getCallback().recordPause();
|
||||||
|
|
||||||
|
transition.resetTransition();
|
||||||
|
|
||||||
|
ActivityUtils.vibrate(getContext(), 1000);
|
||||||
|
};
|
||||||
|
handlerUpdateTotalTime.postDelayed(buttonDelay, buttonDelayDuration);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//To stop a recording
|
||||||
|
if (viewBinding.controllerStop.equals(view)
|
||||||
|
&& event.getAction() == MotionEvent.ACTION_DOWN
|
||||||
|
&& recordingStatus.isRecording()) {
|
||||||
|
|
||||||
|
|
||||||
|
transition.startTransition(buttonDelayDuration);
|
||||||
|
|
||||||
|
ActivityUtils.vibrate(getContext(), 150);
|
||||||
|
viewBinding.controllerStatus.setText(R.string.hold_to_stop);
|
||||||
|
|
||||||
|
buttonDelay = () -> {
|
||||||
|
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||||
|
getCallback().recordStop();
|
||||||
|
ActivityUtils.vibrate(getContext(), 1000);
|
||||||
|
|
||||||
|
transition.resetTransition();
|
||||||
|
};
|
||||||
|
handlerUpdateTotalTime.postDelayed(buttonDelay, buttonDelayDuration);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hide() {
|
||||||
|
viewBinding.controllerContainer.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
viewBinding.controllerContainer.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTotalTime() {
|
||||||
|
TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||||
|
if (trackRecordingService != null) {
|
||||||
|
totalTime = trackRecordingService.getTotalTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTotalTime() {
|
||||||
|
if (totalTime != null) {
|
||||||
|
viewBinding.controllerTotalTime.setText(StringUtils.formatElapsedTimeWithHour(totalTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showStatusSetDefaultText() {
|
||||||
|
viewBinding.controllerStatus.setText(recordingStatus.isPaused() ? R.string.generic_paused : R.string.generic_recording);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Callback getCallback() {
|
||||||
|
return (Callback) getActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
void recordStart();
|
||||||
|
|
||||||
|
void recordPause();
|
||||||
|
|
||||||
|
void recordStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
||||||
|
recordingStatus = status;
|
||||||
|
|
||||||
|
if (!recordingStatus.isRecording()) {
|
||||||
|
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
||||||
|
}
|
||||||
|
if (recordingStatus.isRecordingAndNotPaused()) {
|
||||||
|
handlerUpdateTotalTime.postDelayed(updateTotalTimeRunnable, UI_UPDATE_INTERVAL.toMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
viewBinding.controllerRecord.setImageResource(recordingStatus.isRecordingAndNotPaused() ? R.drawable.ic_button_pause : R.drawable.button_record);
|
||||||
|
viewBinding.controllerRecord.setContentDescription(getContext().getString(recordingStatus.isRecordingAndNotPaused() ? R.string.image_pause : R.string.image_record));
|
||||||
|
|
||||||
|
viewBinding.controllerStop.setEnabled(recordingStatus.isRecording());
|
||||||
|
|
||||||
|
viewBinding.controllerStatus.setVisibility(recordingStatus.isRecording() ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
if (recordingStatus.isRecording()) {
|
||||||
|
viewBinding.controllerStatus.setTextColor(getContext().getResources().getColor(recordingStatus.isPaused() ? android.R.color.white : R.color.recording_text));
|
||||||
|
showStatusSetDefaultText();
|
||||||
|
}
|
||||||
|
|
||||||
|
viewBinding.controllerTotalTime.setVisibility(recordingStatus.isRecording() ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
|
||||||
|
if (recordingStatus.isRecording()) {
|
||||||
|
updateTotalTime();
|
||||||
|
setTotalTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -175,7 +175,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
insertMarkerMenuItem = menu.findItem(R.id.marker_list_insert_marker);
|
insertMarkerMenuItem = menu.findItem(R.id.marker_list_insert_marker);
|
||||||
|
|
||||||
searchMenuItem = menu.findItem(R.id.marker_list_search);
|
searchMenuItem = menu.findItem(R.id.marker_list_search);
|
||||||
ActivityUtils.configureSearchWidget(this, searchMenuItem, null);
|
ActivityUtils.configureSearchWidget(this, searchMenuItem);
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
@@ -261,15 +261,16 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
|
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||||
|
if (!searchView.isIconified()) {
|
||||||
|
searchView.setIconified(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (loaderCallbacks.getSearchQuery() != null) {
|
if (loaderCallbacks.getSearchQuery() != null) {
|
||||||
loaderCallbacks.setSearch(null);
|
loaderCallbacks.setSearch(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
|
||||||
if (!searchView.isIconified()) {
|
|
||||||
searchView.setIconified(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,260 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.dennisguse.opentracks;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.graphics.drawable.TransitionDrawable;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.databinding.TrackControllerBinding;
|
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
|
||||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track controller for record, pause, resume, and stop.
|
|
||||||
*
|
|
||||||
* @author Jimmy Shih
|
|
||||||
*/
|
|
||||||
//TODO Subscribe to TrackRecordingService.recordingStatus
|
|
||||||
//TODO Could be a fragment
|
|
||||||
public class TrackController implements View.OnTouchListener {
|
|
||||||
|
|
||||||
private static final String TAG = TrackController.class.getSimpleName();
|
|
||||||
|
|
||||||
private static final Duration UI_UPDATE_INTERVAL = Duration.ofSeconds(1);
|
|
||||||
|
|
||||||
private final Activity activity;
|
|
||||||
private final TrackRecordingServiceConnection trackRecordingServiceConnection;
|
|
||||||
private final Handler handlerUpdateTotalTime = new Handler();
|
|
||||||
private final boolean alwaysShow;
|
|
||||||
|
|
||||||
private Runnable buttonDelay;
|
|
||||||
private final int buttonDelayDuration;
|
|
||||||
|
|
||||||
private final TrackControllerBinding viewBinding;
|
|
||||||
private final TransitionDrawable transition;
|
|
||||||
|
|
||||||
|
|
||||||
private TrackRecordingService.RecordingStatus recordingStatus;
|
|
||||||
private Duration totalTime;
|
|
||||||
|
|
||||||
private final Callback callback;
|
|
||||||
|
|
||||||
// A runnable to update the total time.
|
|
||||||
private final Runnable updateTotalTimeRunnable = new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (isResumed() && recordingStatus.isRecordingAndNotPaused()) {
|
|
||||||
updateTotalTime();
|
|
||||||
setTotalTime();
|
|
||||||
handlerUpdateTotalTime.postDelayed(this, UI_UPDATE_INTERVAL.toMillis());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
TrackController(Activity activity, TrackControllerBinding viewBinding, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean alwaysShow, Callback callback) {
|
|
||||||
this.activity = activity;
|
|
||||||
this.viewBinding = viewBinding;
|
|
||||||
this.trackRecordingServiceConnection = trackRecordingServiceConnection;
|
|
||||||
this.alwaysShow = alwaysShow;
|
|
||||||
this.callback = callback;
|
|
||||||
|
|
||||||
buttonDelayDuration = activity.getResources().getInteger(R.integer.buttonDelayMillis);
|
|
||||||
|
|
||||||
viewBinding.trackControllerRecord.setOnTouchListener(this);
|
|
||||||
viewBinding.trackControllerRecord.setOnClickListener((view) -> {
|
|
||||||
if (buttonDelay != null || recordingStatus.isRecordingAndNotPaused()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback.recordStart();
|
|
||||||
});
|
|
||||||
|
|
||||||
viewBinding.trackControllerStop.setOnTouchListener(this);
|
|
||||||
transition = (TransitionDrawable) viewBinding.trackControllerContainer.getBackground();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility") //We do accessibility manually.
|
|
||||||
@Override
|
|
||||||
public boolean onTouch(final View view, MotionEvent event) {
|
|
||||||
if (!isResumed()) {
|
|
||||||
Log.w(TAG, "The UI is already gone, no need to update anything; seen on Android 7.1.2");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (event.getAction() == MotionEvent.ACTION_MOVE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buttonDelay != null && event.getAction() == MotionEvent.ACTION_UP) {
|
|
||||||
handlerUpdateTotalTime.removeCallbacks(buttonDelay);
|
|
||||||
buttonDelay = null;
|
|
||||||
view.setPressed(false);
|
|
||||||
|
|
||||||
transition.resetTransition();
|
|
||||||
|
|
||||||
showStatusSetDefaultText();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Trigger button pressed animation
|
|
||||||
view.setPressed(true);
|
|
||||||
|
|
||||||
//To pause a recording
|
|
||||||
if (viewBinding.trackControllerRecord.equals(view)
|
|
||||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
|
||||||
&& recordingStatus.isRecordingAndNotPaused()) {
|
|
||||||
|
|
||||||
transition.startTransition(buttonDelayDuration);
|
|
||||||
|
|
||||||
ActivityUtils.vibrate(activity, 150);
|
|
||||||
viewBinding.trackControllerStatus.setText(R.string.hold_to_pause);
|
|
||||||
|
|
||||||
buttonDelay = () -> {
|
|
||||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
|
||||||
view.performClick();
|
|
||||||
callback.recordPause();
|
|
||||||
|
|
||||||
transition.resetTransition();
|
|
||||||
|
|
||||||
ActivityUtils.vibrate(activity, 1000);
|
|
||||||
};
|
|
||||||
handlerUpdateTotalTime.postDelayed(buttonDelay, buttonDelayDuration);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//To stop a recording
|
|
||||||
if (viewBinding.trackControllerStop.equals(view)
|
|
||||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
|
||||||
&& recordingStatus.isRecording()) {
|
|
||||||
|
|
||||||
|
|
||||||
transition.startTransition(buttonDelayDuration);
|
|
||||||
|
|
||||||
ActivityUtils.vibrate(activity, 150);
|
|
||||||
viewBinding.trackControllerStatus.setText(R.string.hold_to_stop);
|
|
||||||
|
|
||||||
buttonDelay = () -> {
|
|
||||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
|
||||||
callback.recordStop();
|
|
||||||
ActivityUtils.vibrate(activity, 1000);
|
|
||||||
|
|
||||||
transition.resetTransition();
|
|
||||||
};
|
|
||||||
handlerUpdateTotalTime.postDelayed(buttonDelay, buttonDelayDuration);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update(TrackRecordingService.RecordingStatus recordingStatus) {
|
|
||||||
if (!isResumed()) {
|
|
||||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.recordingStatus = recordingStatus;
|
|
||||||
boolean isRecording = recordingStatus.isRecording();
|
|
||||||
boolean isRecordingPaused = recordingStatus.isPaused();
|
|
||||||
boolean visible = alwaysShow || isRecording;
|
|
||||||
viewBinding.trackControllerContainer.setVisibility(visible ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
if (!visible) {
|
|
||||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRecording && !isRecordingPaused) {
|
|
||||||
handlerUpdateTotalTime.postDelayed(updateTotalTimeRunnable, UI_UPDATE_INTERVAL.toMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
viewBinding.trackControllerRecord.setImageResource(isRecording && !isRecordingPaused ? R.drawable.ic_button_pause : R.drawable.button_record);
|
|
||||||
viewBinding.trackControllerRecord.setContentDescription(activity.getString(isRecording && !isRecordingPaused ? R.string.image_pause : R.string.image_record));
|
|
||||||
|
|
||||||
viewBinding.trackControllerStop.setEnabled(isRecording);
|
|
||||||
|
|
||||||
viewBinding.trackControllerStatus.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
|
||||||
if (isRecording) {
|
|
||||||
viewBinding.trackControllerStatus.setTextColor(activity.getResources().getColor(isRecordingPaused ? android.R.color.white : R.color.recording_text));
|
|
||||||
showStatusSetDefaultText();
|
|
||||||
}
|
|
||||||
|
|
||||||
viewBinding.trackControllerTotalTime.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
|
||||||
|
|
||||||
if (recordingStatus.isRecording()) {
|
|
||||||
updateTotalTime();
|
|
||||||
setTotalTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onResume(TrackRecordingService.RecordingStatus recordingStatus) {
|
|
||||||
this.recordingStatus = recordingStatus;
|
|
||||||
update(recordingStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onPause() {
|
|
||||||
recordingStatus = null;
|
|
||||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hide() {
|
|
||||||
viewBinding.trackControllerContainer.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void show() {
|
|
||||||
viewBinding.trackControllerContainer.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTotalTime() {
|
|
||||||
TrackRecordingService trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
|
||||||
if (trackRecordingService != null) {
|
|
||||||
totalTime = trackRecordingService.getTotalTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setTotalTime() {
|
|
||||||
if (totalTime != null) {
|
|
||||||
viewBinding.trackControllerTotalTime.setText(StringUtils.formatElapsedTimeWithHour(totalTime));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showStatusSetDefaultText() {
|
|
||||||
viewBinding.trackControllerStatus.setText(recordingStatus.isPaused() ? R.string.generic_paused : R.string.generic_recording);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isResumed() {
|
|
||||||
return recordingStatus != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Callback {
|
|
||||||
void recordStart();
|
|
||||||
|
|
||||||
void recordPause();
|
|
||||||
|
|
||||||
void recordStop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2013 Google Inc.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.dennisguse.opentracks;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.PowerManager;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
|
||||||
import de.dennisguse.opentracks.databinding.TrackDeleteBinding;
|
|
||||||
import de.dennisguse.opentracks.util.SystemUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An activity for deleting tracks.
|
|
||||||
*
|
|
||||||
* @author Jimmy Shih
|
|
||||||
*/
|
|
||||||
public class TrackDeleteActivity extends AbstractActivity {
|
|
||||||
|
|
||||||
public static final String EXTRA_TRACK_IDS = "track_ids";
|
|
||||||
|
|
||||||
private List<Track.Id> trackIds;
|
|
||||||
|
|
||||||
private Thread deleteThread;
|
|
||||||
|
|
||||||
private TrackDeleteBinding viewBinding;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
setResult(RESULT_CANCELED);
|
|
||||||
|
|
||||||
Intent intent = getIntent();
|
|
||||||
trackIds = intent.getParcelableArrayListExtra(EXTRA_TRACK_IDS);
|
|
||||||
deleteThread = new Thread(() -> {
|
|
||||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(TrackDeleteActivity.this);
|
|
||||||
|
|
||||||
PowerManager.WakeLock wakeLock = SystemUtils.acquireWakeLock(TrackDeleteActivity.this, null);
|
|
||||||
contentProviderUtils.deleteTracks(TrackDeleteActivity.this, trackIds);
|
|
||||||
|
|
||||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
|
||||||
if (Thread.interrupted()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
runOnUiThread(TrackDeleteActivity.this::onAsyncTaskCompleted);
|
|
||||||
});
|
|
||||||
deleteThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
viewBinding = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
if (deleteThread.getState() == Thread.State.TERMINATED) {
|
|
||||||
onAsyncTaskCompleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
deleteThread.interrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected View getRootView() {
|
|
||||||
viewBinding = TrackDeleteBinding.inflate(getLayoutInflater());
|
|
||||||
return viewBinding.getRoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onAsyncTaskCompleted() {
|
|
||||||
viewBinding.progressbar.setVisibility(View.INVISIBLE);
|
|
||||||
setResult(RESULT_OK);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -71,14 +71,15 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
|
|||||||
*
|
*
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
public class TrackListActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackController.Callback {
|
public class TrackListActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, ControllerFragment.Callback {
|
||||||
|
|
||||||
private static final String TAG = TrackListActivity.class.getSimpleName();
|
private static final String TAG = TrackListActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
protected static final int GPS_REQUEST_CODE = 6;
|
||||||
|
|
||||||
// The following are set in onCreate
|
// The following are set in onCreate
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
private TrackController trackController;
|
|
||||||
private ResourceCursorAdapter resourceCursorAdapter;
|
private ResourceCursorAdapter resourceCursorAdapter;
|
||||||
|
|
||||||
private TrackListBinding viewBinding;
|
private TrackListBinding viewBinding;
|
||||||
@@ -118,7 +119,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
TrackListActivity.this.invalidateOptionsMenu();
|
TrackListActivity.this.invalidateOptionsMenu();
|
||||||
loaderCallbacks.restart();
|
loaderCallbacks.restart();
|
||||||
trackController.onResume(recordingStatus);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,9 +132,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
private final Runnable bindChangedCallback = new Runnable() {
|
private final Runnable bindChangedCallback = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// After binding changes (e.g., becomes available), update the total time in trackController.
|
|
||||||
runOnUiThread(() -> trackController.update(recordingStatus));
|
|
||||||
|
|
||||||
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||||
if (service == null) {
|
if (service == null) {
|
||||||
Log.e(TAG, "service not available to start gps or a new recording");
|
Log.e(TAG, "service not available to start gps or a new recording");
|
||||||
@@ -170,14 +167,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||||
|
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||||
trackController = new TrackController(this, viewBinding.trackControllerContainer, trackRecordingServiceConnection, true, this);
|
|
||||||
|
|
||||||
|
|
||||||
// Show trackController when search dialog is dismissed
|
|
||||||
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
|
|
||||||
if (searchManager != null) {
|
|
||||||
searchManager.setOnDismissListener(() -> trackController.show());
|
|
||||||
}
|
|
||||||
|
|
||||||
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
|
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
|
||||||
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> {
|
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> {
|
||||||
@@ -250,14 +239,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
trackRecordingServiceConnection.startConnection(this);
|
trackRecordingServiceConnection.startConnection(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
|
|
||||||
// Update UI
|
|
||||||
trackController.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -265,7 +246,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
// Update UI
|
// Update UI
|
||||||
this.invalidateOptionsMenu();
|
this.invalidateOptionsMenu();
|
||||||
LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks);
|
LoaderManager.getInstance(this).restartLoader(0, null, loaderCallbacks);
|
||||||
trackController.onResume(recordingStatus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -298,7 +278,13 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
getMenuInflater().inflate(R.menu.track_list, menu);
|
getMenuInflater().inflate(R.menu.track_list, menu);
|
||||||
|
|
||||||
searchMenuItem = menu.findItem(R.id.track_list_search);
|
searchMenuItem = menu.findItem(R.id.track_list_search);
|
||||||
ActivityUtils.configureSearchWidget(this, searchMenuItem, trackController);
|
SearchView searchView = ActivityUtils.configureSearchWidget(this, searchMenuItem);
|
||||||
|
searchView.setOnCloseListener(() -> {
|
||||||
|
searchView.clearFocus();
|
||||||
|
searchMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
@@ -306,7 +292,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
updateMenuItems(gpsStatusValue.isGpsStarted(), recordingStatus.isRecording());
|
updateGpsMenuItem(gpsStatusValue.isGpsStarted(), recordingStatus.isRecording());
|
||||||
|
|
||||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||||
searchView.setQuery("", false);
|
searchView.setQuery("", false);
|
||||||
@@ -355,6 +341,12 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.track_list_search) {
|
||||||
|
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||||
|
searchView.setIconified(false);
|
||||||
|
searchMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
|
}
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,17 +358,23 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
return super.onKeyUp(keyCode, event);
|
return super.onKeyUp(keyCode, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void overridePendingTransition(int enterAnim, int exitAnim) {
|
||||||
|
//Disable animations as it is weird going into searchMode; looks okay for SplashScreen.
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
|
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||||
|
if (!searchView.isIconified()) {
|
||||||
|
searchView.setIconified(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (loaderCallbacks.getSearchQuery() != null) {
|
if (loaderCallbacks.getSearchQuery() != null) {
|
||||||
loaderCallbacks.setSearch(null);
|
loaderCallbacks.setSearch(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
|
||||||
if (!searchView.isIconified()) {
|
|
||||||
searchView.setIconified(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,20 +394,13 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
loaderCallbacks.setSearch(searchQuery);
|
loaderCallbacks.setSearch(searchQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSearchRequested() {
|
|
||||||
// Hide trackController when search dialog is shown
|
|
||||||
trackController.hide();
|
|
||||||
return super.onSearchRequested();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
|
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
|
||||||
return trackRecordingServiceConnection;
|
return trackRecordingServiceConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onTrackDeleted() {
|
protected void onDeleteConfirmed() {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,22 +416,13 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the menu items with not fixed icon for gps option.
|
|
||||||
*
|
|
||||||
* @param isGpsStarted true if gps is started
|
|
||||||
* @param isRecording true if recording
|
|
||||||
*/
|
|
||||||
private void updateMenuItems(boolean isGpsStarted, boolean isRecording) {
|
|
||||||
updateGpsMenuItem(isGpsStarted, isRecording);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the menu items with the icon specified.
|
* Updates the menu items with the icon specified.
|
||||||
*
|
*
|
||||||
* @param isGpsStarted true if gps is started
|
* @param isGpsStarted true if gps is started
|
||||||
* @param isRecording true if recording
|
* @param isRecording true if recording
|
||||||
*/
|
*/
|
||||||
|
//TODO Check if if can be avoided to call this outside of onGpsStatusChanged()
|
||||||
private void updateGpsMenuItem(boolean isGpsStarted, boolean isRecording) {
|
private void updateGpsMenuItem(boolean isGpsStarted, boolean isRecording) {
|
||||||
if (startGpsMenuItem != null) {
|
if (startGpsMenuItem != null) {
|
||||||
startGpsMenuItem.setVisible(!isRecording);
|
startGpsMenuItem.setVisible(!isRecording);
|
||||||
@@ -479,7 +461,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (itemId == R.id.list_context_menu_edit) {
|
if (itemId == R.id.list_context_menu_edit) {
|
||||||
Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]);
|
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]);
|
||||||
@@ -491,6 +472,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
deleteTracks(trackIds);
|
deleteTracks(trackIds);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemId == R.id.list_context_menu_select_all) {
|
if (itemId == R.id.list_context_menu_select_all) {
|
||||||
for (int i = 0; i < viewBinding.trackList.getCount(); i++) {
|
for (int i = 0; i < viewBinding.trackList.getCount(); i++) {
|
||||||
viewBinding.trackList.setItemChecked(i, true);
|
viewBinding.trackList.setItemChecked(i, true);
|
||||||
@@ -558,25 +540,25 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
public void recordStart() {
|
public void recordStart() {
|
||||||
if (recordingStatus.getTrackId() == null) {
|
if (recordingStatus.getTrackId() == null) {
|
||||||
// Not recording -> Recording
|
// Not recording -> Recording
|
||||||
updateMenuItems(false, true);
|
updateGpsMenuItem(false, true);
|
||||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
||||||
startActivity(newIntent);
|
startActivity(newIntent);
|
||||||
} else if (recordingStatus.isPaused()) {
|
} else if (recordingStatus.isPaused()) {
|
||||||
// Paused -> Resume
|
// Paused -> Resume
|
||||||
updateMenuItems(false, true);
|
updateGpsMenuItem(false, true);
|
||||||
trackRecordingServiceConnection.resumeTrack();
|
trackRecordingServiceConnection.resumeTrack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void recordPause() {
|
public void recordPause() {
|
||||||
updateMenuItems(false, true);
|
updateGpsMenuItem(false, true);
|
||||||
trackRecordingServiceConnection.pauseTrack();
|
trackRecordingServiceConnection.pauseTrack();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void recordStop() {
|
public void recordStop() {
|
||||||
updateMenuItems(false, false);
|
updateGpsMenuItem(false, false);
|
||||||
trackRecordingServiceConnection.stopRecording(TrackListActivity.this);
|
trackRecordingServiceConnection.stopRecording(TrackListActivity.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,10 +567,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
|||||||
updateGpsMenuItem(true, recordingStatus.isRecording());
|
updateGpsMenuItem(true, recordingStatus.isRecording());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
||||||
recordingStatus = status;
|
recordingStatus = status;
|
||||||
|
|
||||||
trackController.update(recordingStatus);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import de.dennisguse.opentracks.fragments.ChartFragment;
|
|||||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||||
import de.dennisguse.opentracks.fragments.IntervalsFragment;
|
import de.dennisguse.opentracks.fragments.IntervalsFragment;
|
||||||
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
|
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
|
||||||
|
import de.dennisguse.opentracks.services.TrackDeleteService;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
@@ -56,7 +57,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
|||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
//TODO Should not use TrackRecordingServiceConnection; only used to determine if there is NO current recording, to enable resume functionality.
|
//TODO Should not use TrackRecordingServiceConnection; only used to determine if there is NO current recording, to enable resume functionality.
|
||||||
public class TrackRecordedActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackActivityDataHubInterface {
|
public class TrackRecordedActivity extends AbstractTrackDeleteActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackActivityDataHubInterface {
|
||||||
|
|
||||||
private static final String TAG = TrackRecordedActivity.class.getSimpleName();
|
private static final String TAG = TrackRecordedActivity.class.getSimpleName();
|
||||||
|
|
||||||
@@ -248,9 +249,17 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
|||||||
return recordingStatus.getTrackId();
|
return recordingStatus.getTrackId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onTrackDeleteStatus(TrackDeleteService.DeleteStatus deleteStatus) {
|
||||||
|
super.onTrackDeleteStatus(deleteStatus);
|
||||||
|
Log.e("probando", "trackId: " + trackId);
|
||||||
|
if (deleteStatus.isDeleted(trackId)) {
|
||||||
|
runOnUiThread(this::finish);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onTrackDeleted() {
|
protected void onDeleteConfirmed() {
|
||||||
runOnUiThread(this::finish);
|
runOnUiThread(this::finish);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import de.dennisguse.opentracks.util.TrackUtils;
|
|||||||
*/
|
*/
|
||||||
//NOTE: This activity does NOT react to preference changes of R.string.recording_track_id_key.
|
//NOTE: This activity does NOT react to preference changes of R.string.recording_track_id_key.
|
||||||
//This mode of communication should be removed anyhow.
|
//This mode of communication should be removed anyhow.
|
||||||
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackActivityDataHubInterface, TrackController.Callback {
|
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackActivityDataHubInterface, ControllerFragment.Callback {
|
||||||
|
|
||||||
public static final String EXTRA_TRACK_ID = "track_id";
|
public static final String EXTRA_TRACK_ID = "track_id";
|
||||||
|
|
||||||
@@ -56,7 +56,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
private TrackDataHub trackDataHub;
|
private TrackDataHub trackDataHub;
|
||||||
private TrackController trackController;
|
|
||||||
|
|
||||||
private TrackRecordingBinding viewBinding;
|
private TrackRecordingBinding viewBinding;
|
||||||
|
|
||||||
@@ -90,8 +89,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
// A recording track is on.
|
// A recording track is on.
|
||||||
trackDataHub.loadTrack(trackId);
|
trackDataHub.loadTrack(trackId);
|
||||||
trackDataHub.setRecordingStatus(recordingStatus);
|
trackDataHub.setRecordingStatus(recordingStatus);
|
||||||
|
|
||||||
trackController.onResume(recordingStatus);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -148,8 +145,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
||||||
}
|
}
|
||||||
|
|
||||||
trackController = new TrackController(this, viewBinding.trackControllerContainer, trackRecordingServiceConnection, false, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -214,7 +209,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
//TODO Pass recordingStatus directly to them
|
//TODO Pass recordingStatus directly to them
|
||||||
trackDataHub.loadTrack(trackId);
|
trackDataHub.loadTrack(trackId);
|
||||||
trackDataHub.setRecordingStatus(recordingStatus);
|
trackDataHub.setRecordingStatus(recordingStatus);
|
||||||
trackController.onResume(recordingStatus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -233,12 +227,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
outState.putParcelable(EXTRA_TRACK_ID, trackId);
|
outState.putParcelable(EXTRA_TRACK_ID, trackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
trackController.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
@@ -406,7 +394,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
||||||
recordingStatus = status;
|
recordingStatus = status;
|
||||||
|
|
||||||
trackController.update(recordingStatus);
|
|
||||||
trackDataHub.setRecordingStatus(recordingStatus);
|
trackDataHub.setRecordingStatus(recordingStatus);
|
||||||
|
|
||||||
setLockscreenPolicy();
|
setLockscreenPolicy();
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ import androidx.core.view.GestureDetectorCompat;
|
|||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.MarkerDetailActivity;
|
import de.dennisguse.opentracks.MarkerDetailActivity;
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
@@ -424,7 +424,7 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateDimensions();
|
updateDimensions();
|
||||||
updatePaths();
|
updateSeries();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,7 +471,7 @@ public class ChartView extends View {
|
|||||||
private void zoomIn() {
|
private void zoomIn() {
|
||||||
if (canZoomIn()) {
|
if (canZoomIn()) {
|
||||||
zoomLevel++;
|
zoomLevel++;
|
||||||
updatePaths();
|
updateSeries();
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -486,7 +486,7 @@ public class ChartView extends View {
|
|||||||
scrollX = maxWidth;
|
scrollX = maxWidth;
|
||||||
scrollTo(scrollX, 0);
|
scrollTo(scrollX, 0);
|
||||||
}
|
}
|
||||||
updatePaths();
|
updateSeries();
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -853,63 +853,49 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates paths.
|
|
||||||
* The path needs to be updated any time after the data or the dimensions change.
|
* The path needs to be updated any time after the data or the dimensions change.
|
||||||
*/
|
*/
|
||||||
private void updatePaths() {
|
private void updateSeries() {
|
||||||
synchronized (chartPoints) {
|
synchronized (chartPoints) {
|
||||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
seriesList.stream().forEach(this::updateSerie);
|
||||||
chartValueSeries.getPath().reset();
|
|
||||||
}
|
|
||||||
drawPaths();
|
|
||||||
closePaths();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawPaths() {
|
private void updateSerie(ChartValueSeries series) {
|
||||||
boolean[] hasMoved = new boolean[seriesList.size()];
|
final int yCorner = topBorder + effectiveHeight;
|
||||||
|
final Path path = series.getPath();
|
||||||
|
|
||||||
for (ChartPoint dataPoint : chartPoints) {
|
boolean drawFirstPoint = false;
|
||||||
for (int i = 0; i < seriesList.size(); i++) {
|
path.rewind();
|
||||||
ChartValueSeries chartValueSeries = seriesList.get(i);
|
|
||||||
|
|
||||||
if (!chartValueSeries.isChartPointValid(dataPoint)) {
|
Iterator<ChartPoint> iterator = chartPoints.iterator();
|
||||||
continue;
|
while (iterator.hasNext()) {
|
||||||
}
|
ChartPoint point = iterator.next();
|
||||||
|
if (!series.isChartPointValid(point)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
double value = chartValueSeries.extractDataFromChartPoint(dataPoint);
|
double value = series.extractDataFromChartPoint(point);
|
||||||
Path path = chartValueSeries.getPath();
|
int x = getX(point.getTimeOrDistance());
|
||||||
int x = getX(dataPoint.getTimeOrDistance());
|
int y = getY(series, value);
|
||||||
int y = getY(chartValueSeries, value);
|
|
||||||
if (!hasMoved[i]) {
|
// start from lower left corner
|
||||||
hasMoved[i] = true;
|
if (!drawFirstPoint) {
|
||||||
path.moveTo(x, y);
|
path.moveTo(x, yCorner);
|
||||||
} else {
|
drawFirstPoint = true;
|
||||||
path.lineTo(x, y);
|
}
|
||||||
}
|
|
||||||
|
// draw graph
|
||||||
|
path.lineTo(x, y);
|
||||||
|
|
||||||
|
// last point: move to lower right
|
||||||
|
if (!iterator.hasNext()) {
|
||||||
|
path.lineTo(x, yCorner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void closePaths() {
|
// back to lower left corner
|
||||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
path.close();
|
||||||
Optional<ChartPoint> firstValid = chartPoints.stream().filter(chartValueSeries::isChartPointValid)
|
|
||||||
.findFirst();
|
|
||||||
Optional<ChartPoint> lastValid = chartPoints.stream().filter(chartValueSeries::isChartPointValid)
|
|
||||||
.reduce((first, second) -> second);
|
|
||||||
|
|
||||||
if (firstValid.isPresent() && lastValid.isPresent()) {
|
|
||||||
Path path = chartValueSeries.getPath();
|
|
||||||
|
|
||||||
int yCorner = topBorder + effectiveHeight;
|
|
||||||
|
|
||||||
path.lineTo(getX(lastValid.get().getTimeOrDistance()), yCorner);
|
|
||||||
path.lineTo(getX(firstValid.get().getTimeOrDistance()), yCorner);
|
|
||||||
|
|
||||||
double value = chartValueSeries.extractDataFromChartPoint(firstValid.get());
|
|
||||||
path.lineTo(getX(firstValid.get().getTimeOrDistance()), getY(chartValueSeries, value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -961,7 +947,7 @@ public class ChartView extends View {
|
|||||||
width = newWidth;
|
width = newWidth;
|
||||||
height = newHeight;
|
height = newHeight;
|
||||||
updateEffectiveDimensions();
|
updateEffectiveDimensions();
|
||||||
updatePaths();
|
updateSeries();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,6 @@ public class TrackDataHub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
started = false;
|
started = false;
|
||||||
trackStatisticsUpdater = null;
|
|
||||||
|
|
||||||
//Unregister listeners
|
//Unregister listeners
|
||||||
ContentResolver contentResolver = context.getContentResolver();
|
ContentResolver contentResolver = context.getContentResolver();
|
||||||
@@ -168,6 +167,7 @@ public class TrackDataHub {
|
|||||||
handlerThread = null;
|
handlerThread = null;
|
||||||
}
|
}
|
||||||
handler = null;
|
handler = null;
|
||||||
|
trackStatisticsUpdater = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadTrack(final @NonNull Track.Id trackId) {
|
public void loadTrack(final @NonNull Track.Id trackId) {
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ public class Distance {
|
|||||||
return of(distance_km * UnitConversions.KM_TO_M);
|
return of(distance_km * UnitConversions.KM_TO_M);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Distance ofMM(double distance_mm) {
|
||||||
|
return of(distance_mm * UnitConversions.MM_TO_M);
|
||||||
|
}
|
||||||
|
|
||||||
public static Distance one(boolean metricUnit) {
|
public static Distance one(boolean metricUnit) {
|
||||||
if (metricUnit) {
|
if (metricUnit) {
|
||||||
return Distance.ofKilometer(1);
|
return Distance.ofKilometer(1);
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ import de.dennisguse.opentracks.util.UUIDUtils;
|
|||||||
public class ContentProviderUtils {
|
public class ContentProviderUtils {
|
||||||
|
|
||||||
private static final String TAG = ContentProviderUtils.class.getSimpleName();
|
private static final String TAG = ContentProviderUtils.class.getSimpleName();
|
||||||
private static final int MAX_LATITUDE = 90000000;
|
|
||||||
|
|
||||||
// The authority (the first part of the URI) for the app's content provider.
|
// The authority (the first part of the URI) for the app's content provider.
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -161,8 +160,10 @@ public class ContentProviderUtils {
|
|||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void deleteAllTracks(Context context) {
|
public void deleteAllTracks(Context context) {
|
||||||
|
//TODO Both calls should not be necessary
|
||||||
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, null, null);
|
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, null, null);
|
||||||
contentResolver.delete(MarkerColumns.CONTENT_URI, null, null);
|
contentResolver.delete(MarkerColumns.CONTENT_URI, null, null);
|
||||||
|
|
||||||
// Delete tracks last since it triggers a database vaccum call
|
// Delete tracks last since it triggers a database vaccum call
|
||||||
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
|
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
|
||||||
|
|
||||||
@@ -190,6 +191,7 @@ public class ContentProviderUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO Only use for tests; also move to tests.
|
//TODO Only use for tests; also move to tests.
|
||||||
|
@VisibleForTesting
|
||||||
public List<Track> getTracks() {
|
public List<Track> getTracks() {
|
||||||
ArrayList<Track> tracks = new ArrayList<>();
|
ArrayList<Track> tracks = new ArrayList<>();
|
||||||
try (Cursor cursor = getTrackCursor(null, null, TracksColumns._ID)) {
|
try (Cursor cursor = getTrackCursor(null, null, TracksColumns._ID)) {
|
||||||
@@ -203,9 +205,6 @@ public class ContentProviderUtils {
|
|||||||
return tracks;
|
return tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param trackId the track id.
|
|
||||||
*/
|
|
||||||
public Track getTrack(Track.Id trackId) {
|
public Track getTrack(Track.Id trackId) {
|
||||||
if (trackId == null) {
|
if (trackId == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -218,9 +217,6 @@ public class ContentProviderUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param trackUUID the track uuid.
|
|
||||||
*/
|
|
||||||
public Track getTrack(@NonNull UUID trackUUID) {
|
public Track getTrack(@NonNull UUID trackUUID) {
|
||||||
String trackUUIDsearch = UUIDUtils.toHex(trackUUID);
|
String trackUUIDsearch = UUIDUtils.toHex(trackUUID);
|
||||||
try (Cursor cursor = getTrackCursor("hex(" + TracksColumns.UUID + ")=?", new String[]{trackUUIDsearch}, null)) {
|
try (Cursor cursor = getTrackCursor("hex(" + TracksColumns.UUID + ")=?", new String[]{trackUUIDsearch}, null)) {
|
||||||
@@ -609,6 +605,7 @@ public class ContentProviderUtils {
|
|||||||
* @param location the location
|
* @param location the location
|
||||||
* @return trackPoint id if the location is in the track. -1L otherwise.
|
* @return trackPoint id if the location is in the track. -1L otherwise.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public TrackPoint.Id getTrackPointId(Track.Id trackId, Location location) {
|
public TrackPoint.Id getTrackPointId(Track.Id trackId, Location location) {
|
||||||
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)";
|
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)";
|
||||||
String[] selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(location.getTime())};
|
String[] selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(location.getTime())};
|
||||||
@@ -740,6 +737,7 @@ public class ContentProviderUtils {
|
|||||||
return new TrackPointIterator(this, trackId, startTrackPointId);
|
return new TrackPointIterator(this, trackId, startTrackPointId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
private TrackPoint findTrackPointBy(String selection, String[] selectionArgs) {
|
private TrackPoint findTrackPointBy(String selection, String[] selectionArgs) {
|
||||||
try (Cursor cursor = getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns._ID)) {
|
try (Cursor cursor = getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns._ID)) {
|
||||||
if (cursor != null && cursor.moveToNext()) {
|
if (cursor != null && cursor.moveToNext()) {
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ public final class SensorDataCycling {
|
|||||||
return wheelRevolutionsTime;
|
return wheelRevolutionsTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void compute(DistanceSpeed previous, int wheel_circumference_mm) {
|
public void compute(DistanceSpeed previous, Distance wheelCircumference) {
|
||||||
if (hasData() && previous != null && previous.hasData()) {
|
if (hasData() && previous != null && previous.hasData()) {
|
||||||
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
|
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
|
||||||
Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
|
Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
|
||||||
@@ -134,7 +134,7 @@ public final class SensorDataCycling {
|
|||||||
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT16_MAX);
|
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT16_MAX);
|
||||||
wheelDiff = Math.abs(wheelDiff); //HACK for Garmin Speed 2 as some of those seem to count backwards
|
wheelDiff = Math.abs(wheelDiff); //HACK for Garmin Speed 2 as some of those seem to count backwards
|
||||||
|
|
||||||
Distance distance = Distance.of(wheelDiff * wheel_circumference_mm * UnitConversions.MM_TO_M);
|
Distance distance = wheelCircumference.multipliedBy(wheelDiff);
|
||||||
Distance distanceOverall = distance;
|
Distance distanceOverall = distance;
|
||||||
if (previous.hasValue()) {
|
if (previous.hasValue()) {
|
||||||
distanceOverall = distance.plus(previous.getValue().distanceOverall);
|
distanceOverall = distance.plus(previous.getValue().distanceOverall);
|
||||||
|
|||||||
@@ -518,13 +518,12 @@ public class KMLTrackExporter implements TrackExporter {
|
|||||||
return location.getBearing();
|
return location.getBearing();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getCoordinates(Location location, String separator) {
|
private static String getCoordinates(Location location, String separator) {
|
||||||
StringBuilder builder = new StringBuilder();
|
String result = location.getLongitude() + separator + location.getLatitude();
|
||||||
builder.append(location.getLongitude()).append(separator).append(location.getLatitude());
|
|
||||||
if (location.hasAltitude()) {
|
if (location.hasAltitude()) {
|
||||||
builder.append(separator).append(location.getAltitude());
|
result += separator + location.getAltitude();
|
||||||
}
|
}
|
||||||
return builder.toString();
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
package de.dennisguse.opentracks.services;
|
||||||
|
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
|
|
||||||
|
public class TrackDeleteService extends Service {
|
||||||
|
|
||||||
|
private static final String CHANNEL_ID = TrackDeleteService.class.getSimpleName();
|
||||||
|
private static final int NOTIFICATION_ID = 1;
|
||||||
|
|
||||||
|
static final String EXTRA_TRACK_IDS = "extra_track_ids";
|
||||||
|
|
||||||
|
private final Binder binder = new Binder();
|
||||||
|
private ExecutorService serviceExecutor;
|
||||||
|
private MutableLiveData<DeleteStatus> deleteResultObservable;
|
||||||
|
private NotificationManager notificationManager;
|
||||||
|
private NotificationCompat.Builder notificationBuilder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
serviceExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
deleteResultObservable = new MutableLiveData<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (serviceExecutor != null) {
|
||||||
|
serviceExecutor.shutdownNow();
|
||||||
|
serviceExecutor = null;
|
||||||
|
}
|
||||||
|
deleteResultObservable = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
ArrayList<Track.Id> trackIds = intent.getParcelableArrayListExtra(EXTRA_TRACK_IDS);
|
||||||
|
createAndShowNotification(trackIds.size());
|
||||||
|
deleteTracks(trackIds);
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteTracks(@NonNull ArrayList<Track.Id> trackIds) {
|
||||||
|
sendResult(null,0, trackIds.size());
|
||||||
|
serviceExecutor.execute(() -> {
|
||||||
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
||||||
|
for (int i = 0; i < trackIds.size(); i++) {
|
||||||
|
updateNotification(i + 1, trackIds.size());
|
||||||
|
contentProviderUtils.deleteTrack(this, trackIds.get(i));
|
||||||
|
sendResult(trackIds.get(i), i + 1, trackIds.size());
|
||||||
|
}
|
||||||
|
stopSelf();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendResult(Track.Id trackId, int deletes, int total) {
|
||||||
|
if (deleteResultObservable != null) {
|
||||||
|
deleteResultObservable.postValue(new DeleteStatus(trackId, deletes, total));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return binder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<DeleteStatus> getDeletingStatusObservable() {
|
||||||
|
return deleteResultObservable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts and shows the notification.
|
||||||
|
*
|
||||||
|
* @param tracksToDelete number of tracks to be deleted.
|
||||||
|
*/
|
||||||
|
private void createAndShowNotification(int tracksToDelete) {
|
||||||
|
notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, this.getString(R.string.app_name), NotificationManager.IMPORTANCE_LOW);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
notificationChannel.setAllowBubbles(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationManager.createNotificationChannel(notificationChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
|
||||||
|
notificationBuilder
|
||||||
|
.setContentTitle(this.getString(R.string.track_delete_progress_message))
|
||||||
|
.setContentText(this.getString(R.string.track_delete_progress, 0, tracksToDelete))
|
||||||
|
.setSmallIcon(R.drawable.ic_logo_color_24dp)
|
||||||
|
.setProgress(tracksToDelete, 0, false);
|
||||||
|
|
||||||
|
startForeground(NOTIFICATION_ID, notificationBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates notification progress.
|
||||||
|
*
|
||||||
|
* @param progress number of tracks already deleted.
|
||||||
|
* @param total total of tracks to be deleted.
|
||||||
|
*/
|
||||||
|
private void updateNotification(int progress, int total) {
|
||||||
|
notificationBuilder.setProgress(total, progress, false);
|
||||||
|
notificationBuilder.setContentText(this.getString(R.string.track_delete_progress, progress, total));
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Binder extends android.os.Binder {
|
||||||
|
|
||||||
|
private Binder() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrackDeleteService getService() {
|
||||||
|
return TrackDeleteService.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DeleteStatus {
|
||||||
|
private int progress;
|
||||||
|
private int max;
|
||||||
|
private Track.Id trackId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param trackId Track.Id just deleted if any.
|
||||||
|
* @param progress number of deletes.
|
||||||
|
* @param max total of deletes to be done.
|
||||||
|
*/
|
||||||
|
private DeleteStatus(@Nullable Track.Id trackId, int progress, int max) {
|
||||||
|
this.trackId = trackId;
|
||||||
|
this.progress = progress;
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFinished() {
|
||||||
|
return progress == max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDeleted(Track.Id trackId) {
|
||||||
|
return this.trackId != null && this.trackId.equals(trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DeleteStatus{" +
|
||||||
|
"number of deletes=" + progress +
|
||||||
|
", total=" + max +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package de.dennisguse.opentracks.services;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.BuildConfig;
|
||||||
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
|
|
||||||
|
import static de.dennisguse.opentracks.services.TrackDeleteService.EXTRA_TRACK_IDS;
|
||||||
|
|
||||||
|
public class TrackDeleteServiceConnection implements ServiceConnection {
|
||||||
|
|
||||||
|
final private Listener listener;
|
||||||
|
private TrackDeleteService trackDeleteService;
|
||||||
|
|
||||||
|
public TrackDeleteServiceConnection(@NonNull Listener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
|
trackDeleteService = ((TrackDeleteService.Binder) service).getService();
|
||||||
|
listener.connected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
trackDeleteService = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startAndBind(Context context, ArrayList<Track.Id> trackIds) {
|
||||||
|
if (trackDeleteService != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(context, TrackDeleteService.class)
|
||||||
|
.putParcelableArrayListExtra(EXTRA_TRACK_IDS, trackIds);
|
||||||
|
context.startService(intent);
|
||||||
|
|
||||||
|
bind(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(Context context) {
|
||||||
|
if (trackDeleteService != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.bindService(new Intent(context, TrackDeleteService.class), this, BuildConfig.DEBUG ? Context.BIND_DEBUG_UNBIND : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unbind(Context context) {
|
||||||
|
context.unbindService(this);
|
||||||
|
trackDeleteService = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrackDeleteService getServiceIfBound() {
|
||||||
|
return trackDeleteService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Listener {
|
||||||
|
void connected();
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -25,6 +25,7 @@ import android.util.Log;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorData;
|
import de.dennisguse.opentracks.content.sensor.SensorData;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||||
@@ -57,7 +58,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
|||||||
private boolean started = false;
|
private boolean started = false;
|
||||||
|
|
||||||
private final SharedPreferences sharedPreferences;
|
private final SharedPreferences sharedPreferences;
|
||||||
private int preferenceWheelCircumference;
|
private Distance preferenceWheelCircumference;
|
||||||
|
|
||||||
private final BluetoothConnectionManager.HeartRate heartRate = new BluetoothConnectionManager.HeartRate(this);
|
private final BluetoothConnectionManager.HeartRate heartRate = new BluetoothConnectionManager.HeartRate(this);
|
||||||
private final BluetoothConnectionManager.CyclingCadence cyclingCadence = new BluetoothConnectionManager.CyclingCadence(this);
|
private final BluetoothConnectionManager.CyclingCadence cyclingCadence = new BluetoothConnectionManager.CyclingCadence(this);
|
||||||
|
|||||||
@@ -138,11 +138,6 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(@NonNull TrackRecordingService trackRecordingService) {
|
public void run(@NonNull TrackRecordingService trackRecordingService) {
|
||||||
if (trackRecordingService == null) {
|
|
||||||
Log.e(TAG, "TrackRecordingService is null.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
announce(trackRecordingService.getRecordingTrackId(), trackRecordingService.getTrackStatistics());
|
announce(trackRecordingService.getRecordingTrackId(), trackRecordingService.getTrackStatistics());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ package de.dennisguse.opentracks.settings;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.speech.tts.TextToSpeech;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
@@ -93,6 +95,9 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
|||||||
|
|
||||||
public static class PrefsFragment extends PreferenceFragmentCompat {
|
public static class PrefsFragment extends PreferenceFragmentCompat {
|
||||||
|
|
||||||
|
private TextToSpeech tts;
|
||||||
|
private int ttsInitStatus;
|
||||||
|
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||||
@@ -125,6 +130,22 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
|||||||
// Used to forward update from ChooseActivityTypeDialogFragment; TODO Could be replaced with LiveData.
|
// Used to forward update from ChooseActivityTypeDialogFragment; TODO Could be replaced with LiveData.
|
||||||
private ActivityTypePreference.ActivityPreferenceDialog activityPreferenceDialog;
|
private ActivityTypePreference.ActivityPreferenceDialog activityPreferenceDialog;
|
||||||
|
|
||||||
|
private Preference voiceAnnouncements;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
ttsInitStatus = TextToSpeech.ERROR;
|
||||||
|
tts = new TextToSpeech(getContext(), status -> {
|
||||||
|
ttsInitStatus = status;
|
||||||
|
Log.i(TAG, "TextToSpeech initialized with status " + status);
|
||||||
|
|
||||||
|
if (voiceAnnouncements != null) {
|
||||||
|
updateVoiceAnnouncements();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
||||||
@@ -150,6 +171,9 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
|||||||
//Use same value for not set as Androidx ListPreference and EditTextPreference
|
//Use same value for not set as Androidx ListPreference and EditTextPreference
|
||||||
return directory != null ? directory.getName() : getString(R.string.not_set);
|
return directory != null ? directory.getName() : getString(R.string.not_set);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
voiceAnnouncements = findPreference(getString(R.string.voice_frequency_key));
|
||||||
|
updateVoiceAnnouncements();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -208,6 +232,8 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
|||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
trackRecordingServiceConnection.unbind(getContext());
|
trackRecordingServiceConnection.unbind(getContext());
|
||||||
sharedPreferences = null;
|
sharedPreferences = null;
|
||||||
|
voiceAnnouncements = null;
|
||||||
|
tts = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDefaultActivity(String iconValue) {
|
public void setDefaultActivity(String iconValue) {
|
||||||
@@ -296,6 +322,10 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateVoiceAnnouncements() {
|
||||||
|
voiceAnnouncements.setEnabled(ttsInitStatus == TextToSpeech.SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) {
|
||||||
this.recordingStatus = status;
|
this.recordingStatus = status;
|
||||||
if (!status.isRecording() && isAdded()) {
|
if (!status.isRecording() && isAdded()) {
|
||||||
@@ -303,5 +333,4 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ public class TrackStatisticsUpdater {
|
|||||||
|
|
||||||
if (!trackPoint.hasSensorDistance()) {
|
if (!trackPoint.hasSensorDistance()) {
|
||||||
// GPS-based distance/speed
|
// GPS-based distance/speed
|
||||||
Distance movingDistance = lastMovingTrackPoint.distanceToPrevious(trackPoint);
|
Distance movingDistance = trackPoint.distanceToPrevious(lastMovingTrackPoint);
|
||||||
if (movingDistance.lessThan(minGPSDistance) && !trackPoint.isMoving()) {
|
if (movingDistance.lessThan(minGPSDistance) && !trackPoint.isMoving()) {
|
||||||
speedBuffer_mps.reset();
|
speedBuffer_mps.reset();
|
||||||
lastTrackPoint = trackPoint;
|
lastTrackPoint = trackPoint;
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import java.util.ArrayList;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.ContextualActionModeCallback;
|
import de.dennisguse.opentracks.ContextualActionModeCallback;
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.TrackController;
|
|
||||||
|
|
||||||
import static android.content.Context.VIBRATOR_SERVICE;
|
import static android.content.Context.VIBRATOR_SERVICE;
|
||||||
|
|
||||||
@@ -88,7 +87,7 @@ public class ActivityUtils {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void configureSearchWidget(Activity activity, final MenuItem menuItem, final TrackController trackController) {
|
public static SearchView configureSearchWidget(Activity activity, final MenuItem menuItem) {
|
||||||
final SearchView searchView = (SearchView) menuItem.getActionView();
|
final SearchView searchView = (SearchView) menuItem.getActionView();
|
||||||
SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
|
SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
|
||||||
if (searchManager != null) {
|
if (searchManager != null) {
|
||||||
@@ -97,47 +96,12 @@ public class ActivityUtils {
|
|||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Could not retrieve SearchManager.");
|
Log.w(TAG, "Could not retrieve SearchManager.");
|
||||||
}
|
}
|
||||||
searchView.setQueryRefinementEnabled(true);
|
|
||||||
searchView.setSubmitButtonEnabled(true);
|
searchView.setSubmitButtonEnabled(true);
|
||||||
searchView.setOnQueryTextFocusChangeListener((v, hasFocus) -> {
|
return searchView;
|
||||||
// Hide and show trackController when searchable widget has focus/no focus
|
|
||||||
if (trackController != null) {
|
|
||||||
if (hasFocus) {
|
|
||||||
trackController.hide();
|
|
||||||
} else {
|
|
||||||
trackController.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextSubmit(String query) {
|
|
||||||
menuItem.collapseActionView();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextChange(String newText) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onSuggestionSelect(int position) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSuggestionClick(int position) {
|
|
||||||
menuItem.collapseActionView();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void vibrate(Activity activity, int milliseconds) {
|
public static void vibrate(Context context, int milliseconds) {
|
||||||
final Vibrator vibrator = (Vibrator) activity.getSystemService(VIBRATOR_SERVICE);
|
final Vibrator vibrator = (Vibrator) context.getSystemService(VIBRATOR_SERVICE);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
vibrator.vibrate(VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE));
|
vibrator.vibrate(VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -142,9 +142,9 @@ public class PreferencesUtils {
|
|||||||
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_speed_key, getBluetoothSensorAddressNone(context));
|
return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_speed_key, getBluetoothSensorAddressNone(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getWheelCircumference(SharedPreferences sharedPreferences, Context context) {
|
public static Distance getWheelCircumference(SharedPreferences sharedPreferences, Context context) {
|
||||||
final int DEFAULT = Integer.parseInt(context.getResources().getString(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_default));
|
final int DEFAULT = Integer.parseInt(context.getResources().getString(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_default));
|
||||||
return getInt(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, DEFAULT);
|
return Distance.ofMM(getInt(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key, DEFAULT));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getBluetoothCyclingPowerSensorAddress(SharedPreferences sharedPreferences, Context context) {
|
public static String getBluetoothCyclingPowerSensorAddress(SharedPreferences sharedPreferences, Context context) {
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Copyright 2013 Google Inc.
|
||||||
|
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/controller_container"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/controller_background"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/controller_record"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="10"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/image_record"
|
||||||
|
android:src="@drawable/ic_button_record" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="6">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/controller_total_time"
|
||||||
|
style="@style/TextSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
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"
|
||||||
|
android:layout_above="@id/controller_total_time"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:singleLine="true" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/controller_stop"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="10"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/image_stop"
|
||||||
|
android:src="@drawable/button_stop" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
Copyright 2013 Google Inc.
|
|
||||||
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/track_controller_container"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="@drawable/track_controller_background"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingStart="8dp"
|
|
||||||
android:paddingEnd="8dp">
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/track_controller_record"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="10"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:contentDescription="@string/image_record"
|
|
||||||
android:src="@drawable/ic_button_record" />
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="6">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/track_controller_total_time"
|
|
||||||
style="@style/TextSmall"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
android:textColor="@android:color/white" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/track_controller_status"
|
|
||||||
style="@style/TextMicro"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_above="@id/track_controller_total_time"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:singleLine="true" />
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/track_controller_stop"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="10"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:contentDescription="@string/image_stop"
|
|
||||||
android:src="@drawable/button_stop" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</merge>
|
|
||||||
@@ -27,9 +27,11 @@ limitations under the License.
|
|||||||
android:baselineAligned="false"
|
android:baselineAligned="false"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<include
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/track_controller_container"
|
android:id="@+id/controller_fragment"
|
||||||
layout="@layout/track_controller" />
|
android:name="de.dennisguse.opentracks.ControllerFragment"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<ListView
|
<ListView
|
||||||
android:id="@+id/track_list"
|
android:id="@+id/track_list"
|
||||||
|
|||||||
@@ -11,9 +11,11 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<include
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/track_controller_container"
|
android:id="@+id/controller_fragment"
|
||||||
layout="@layout/track_controller" />
|
android:name="de.dennisguse.opentracks.ControllerFragment"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Copyright 2012 Google Inc.
|
||||||
|
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/controller_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/controller_background"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/controller_record"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="10"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/image_record"
|
||||||
|
android:src="@drawable/ic_button_record" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_weight="6">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/controller_total_time"
|
||||||
|
style="@style/TextSmall"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
tools:text="0:12:34" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/controller_status"
|
||||||
|
style="@style/TextMicro"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_above="@id/controller_total_time"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:singleLine="true"
|
||||||
|
tools:text="@string/generic_recording" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/controller_stop"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="10"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/image_stop"
|
||||||
|
android:src="@drawable/button_stop" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
Copyright 2012 Google Inc.
|
|
||||||
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/track_controller_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@drawable/track_controller_background"
|
|
||||||
android:paddingTop="8dp"
|
|
||||||
android:paddingBottom="8dp">
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/track_controller_record"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="10"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:contentDescription="@string/image_record"
|
|
||||||
android:src="@drawable/ic_button_record" />
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_weight="6">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/track_controller_total_time"
|
|
||||||
style="@style/TextSmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textColor="@android:color/white"
|
|
||||||
tools:text="0:12:34" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/track_controller_status"
|
|
||||||
style="@style/TextMicro"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_above="@id/track_controller_total_time"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:gravity="center"
|
|
||||||
android:singleLine="true"
|
|
||||||
tools:text="@string/generic_recording" />
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/track_controller_stop"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="10"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:contentDescription="@string/image_stop"
|
|
||||||
android:src="@drawable/button_stop" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</merge>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progressbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:minHeight="100dp"
|
|
||||||
android:minWidth="100dp" />
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -83,7 +83,9 @@ limitations under the License.
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<include
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/track_controller_container"
|
android:id="@+id/controller_fragment"
|
||||||
layout="@layout/track_controller" />
|
android:name="de.dennisguse.opentracks.ControllerFragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -17,7 +17,9 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<include
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/track_controller_container"
|
android:id="@+id/controller_fragment"
|
||||||
layout="@layout/track_controller" />
|
android:name="de.dennisguse.opentracks.ControllerFragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -20,14 +20,14 @@ limitations under the License.
|
|||||||
android:icon="@drawable/ic_statistics_24dp"
|
android:icon="@drawable/ic_statistics_24dp"
|
||||||
android:title="@string/menu_aggregated_statistics"
|
android:title="@string/menu_aggregated_statistics"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
<!-- Icon and title is overwritten by TrackListActivity -->
|
<!-- Icon, title, and visibility is managed by TrackListActivity -->
|
||||||
<!-- Hide icon as workaround for https://github.com/OpenTracksApp/OpenTracks/issues/200 -->
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/track_list_start_gps"
|
android:id="@+id/track_list_start_gps"
|
||||||
android:icon="@drawable/ic_gps_off_24dp"
|
android:icon="@drawable/ic_gps_off_24dp"
|
||||||
android:title="@string/menu_start_gps"
|
android:title="@string/menu_start_gps"
|
||||||
android:visible="false"
|
android:visible="false"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
<!-- TODO SearchAction is only working if it is shown as action. However, only two slots are available. See #757. -->
|
||||||
<item
|
<item
|
||||||
android:id="@+id/track_list_search"
|
android:id="@+id/track_list_search"
|
||||||
android:title="@string/menu_search"
|
android:title="@string/menu_search"
|
||||||
|
|||||||
@@ -611,4 +611,5 @@ Pokud zařízení GPS hlásí nepřesná data (např. Polohu, rychlost, nadmořs
|
|||||||
<string name="track_distance_notification">Vzdálenost: %1$s</string>
|
<string name="track_distance_notification">Vzdálenost: %1$s</string>
|
||||||
<string name="track_speed_notification">Rychlost: %1$s</string>
|
<string name="track_speed_notification">Rychlost: %1$s</string>
|
||||||
<string name="track_pace_notification">Rychlost: %1$s</string>
|
<string name="track_pace_notification">Rychlost: %1$s</string>
|
||||||
|
<string name="about_third_party_data">Údaje třetích stran</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -124,7 +124,7 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
|
|||||||
<string name="activity_type_sled">Schlittenfahren</string>
|
<string name="activity_type_sled">Schlittenfahren</string>
|
||||||
<string name="activity_type_snow_boarding">Snowboarden</string>
|
<string name="activity_type_snow_boarding">Snowboarden</string>
|
||||||
<string name="activity_type_snow_shoeing">Schneeschuhwandern</string>
|
<string name="activity_type_snow_shoeing">Schneeschuhwandern</string>
|
||||||
<string name="activity_type_speed_walking">Speed-Walking</string>
|
<string name="activity_type_speed_walking">Sportliches Gehen</string>
|
||||||
<string name="activity_type_street_running">Laufen (Straßenuntergrund)</string>
|
<string name="activity_type_street_running">Laufen (Straßenuntergrund)</string>
|
||||||
<string name="activity_type_surfing">Surfen</string>
|
<string name="activity_type_surfing">Surfen</string>
|
||||||
<string name="activity_type_track_cycling">Bahnradfahren</string>
|
<string name="activity_type_track_cycling">Bahnradfahren</string>
|
||||||
@@ -214,9 +214,9 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
|
|||||||
<string name="generic_tracks">Strecken</string>
|
<string name="generic_tracks">Strecken</string>
|
||||||
<string name="generic_yes">Ja</string>
|
<string name="generic_yes">Ja</string>
|
||||||
<!-- Gps -->
|
<!-- Gps -->
|
||||||
<string name="gps_location_access">\"Standortzugriff\"</string>
|
<string name="gps_location_access">„Standortzugriff“</string>
|
||||||
<string name="gps_disabled">GPS ist nicht aktiviert. Tippen Sie hier, um zu %1$s wechseln. Drücken Sie \"Zurück\", um zurückzugehen.</string>
|
<string name="gps_disabled">GPS ist nicht aktiviert. Tippen Sie hier, um zu %1$s wechseln. Drücken Sie „Zurück“, um zurückzugehen.</string>
|
||||||
<string name="gps_disabled_my_location">Der aktuelle Standort kann nicht ermittelt werden. Aktivieren Sie mehr Standortquellen unter \"%1$s\" und vergewissern Sie sich, dass der Himmel sichtbar ist. Bitte versuchen Sie es später erneut.</string>
|
<string name="gps_disabled_my_location">Der aktuelle Standort kann nicht ermittelt werden. Aktivieren Sie mehr Standortquellen unter „%1$s“ und vergewissern Sie sich, dass der Himmel sichtbar ist. Bitte versuchen Sie es später erneut.</string>
|
||||||
<string name="gps_not_found">Das Gerät empfängt kein GPS-Signal</string>
|
<string name="gps_not_found">Das Gerät empfängt kein GPS-Signal</string>
|
||||||
<string name="gps_starting">GPS wird gestartet</string>
|
<string name="gps_starting">GPS wird gestartet</string>
|
||||||
<string name="gps_wait_for_better_signal">Warten auf besseres GPS-Signal</string>
|
<string name="gps_wait_for_better_signal">Warten auf besseres GPS-Signal</string>
|
||||||
@@ -231,7 +231,7 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
|
|||||||
<string name="image_photo_marker">Fotomarkierung</string>
|
<string name="image_photo_marker">Fotomarkierung</string>
|
||||||
<string name="image_record">Aufzeichnen</string>
|
<string name="image_record">Aufzeichnen</string>
|
||||||
<string name="image_stop">Beenden</string>
|
<string name="image_stop">Beenden</string>
|
||||||
<string name="image_track">Track</string>
|
<string name="image_track">Strecke</string>
|
||||||
<!-- Import -->
|
<!-- Import -->
|
||||||
<string name="import_error">%1$d von %2$s aus %3$s importiert</string>
|
<string name="import_error">%1$d von %2$s aus %3$s importiert</string>
|
||||||
<string name="import_no_directory">%1$s ist nicht vorhanden.</string>
|
<string name="import_no_directory">%1$s ist nicht vorhanden.</string>
|
||||||
@@ -292,7 +292,7 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
|
|||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
<string name="search_global_description">Tracks und Markierungen</string>
|
<string name="search_global_description">Tracks und Markierungen</string>
|
||||||
<string name="search_list_empty_message">Keine Tracks oder Markierungen gefunden, die Ihrer Suchanfrage entsprechen</string>
|
<string name="search_list_empty_message">Keine Tracks oder Markierungen gefunden, die Ihrer Suchanfrage entsprechen</string>
|
||||||
<string name="search_list_marker_track_location">Im Track \"%1$s\".</string>
|
<string name="search_list_marker_track_location">Im Track „%1$s“.</string>
|
||||||
<!-- Sensor State -->
|
<!-- Sensor State -->
|
||||||
<string name="sensor_state_battery">Akkustatus</string>
|
<string name="sensor_state_battery">Akkustatus</string>
|
||||||
<string name="sensor_state_cadence">Trittfrequenz</string>
|
<string name="sensor_state_cadence">Trittfrequenz</string>
|
||||||
@@ -325,7 +325,7 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
|
|||||||
<string name="settings_recording_auto_resume_track_timeout_summary">Eine Aufzeichnung nach dem Neustart fortsetzen, wenn der letzte Standort innerhalb der letzten %1$s aufgezeichnet wurde</string>
|
<string name="settings_recording_auto_resume_track_timeout_summary">Eine Aufzeichnung nach dem Neustart fortsetzen, wenn der letzte Standort innerhalb der letzten %1$s aufgezeichnet wurde</string>
|
||||||
<string name="settings_recording_auto_resume_track_timeout_title">Automat. Fortsetzung</string>
|
<string name="settings_recording_auto_resume_track_timeout_title">Automat. Fortsetzung</string>
|
||||||
<string name="settings_recording_default_activity_title">Standardaktivität</string>
|
<string name="settings_recording_default_activity_title">Standardaktivität</string>
|
||||||
<string name="settings_recording_location_frequency_summary">Entfernungs-/Zeitintervall %1$s zwischen zwei Standortaufzeichnungen:</string>
|
<string name="settings_recording_location_frequency_summary">Entfernungs-/Zeitintervall %1$s zwischen zwei Standortaufzeichnungen</string>
|
||||||
<string name="settings_recording_max_recording_distance_summary">Ein neues Segment wird erstellt, wenn die Entfernung zwischen zwei Standorten mehr als %1$s beträgt.</string>
|
<string name="settings_recording_max_recording_distance_summary">Ein neues Segment wird erstellt, wenn die Entfernung zwischen zwei Standorten mehr als %1$s beträgt.</string>
|
||||||
<string name="settings_recording_max_recording_distance_title">Max. Entfernung</string>
|
<string name="settings_recording_max_recording_distance_title">Max. Entfernung</string>
|
||||||
<string name="settings_recording_min_recording_distance_title">Entfernungsintervall aufz.</string>
|
<string name="settings_recording_min_recording_distance_title">Entfernungsintervall aufz.</string>
|
||||||
@@ -379,7 +379,7 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
|
|||||||
<string name="stats_distance">Entfernung</string>
|
<string name="stats_distance">Entfernung</string>
|
||||||
<string name="stats_total_time">Gesamtzeit</string>
|
<string name="stats_total_time">Gesamtzeit</string>
|
||||||
<!-- Track -->
|
<!-- Track -->
|
||||||
<string name="track_name_format">Track %1$d</string>
|
<string name="track_name_format">Strecke %1$d</string>
|
||||||
<string name="track_record_notification">Der Track wird aufgezeichnet…</string>
|
<string name="track_record_notification">Der Track wird aufgezeichnet…</string>
|
||||||
<string name="track_recording_notification_accuracy">Standortgenauigkeit: %1$s</string>
|
<string name="track_recording_notification_accuracy">Standortgenauigkeit: %1$s</string>
|
||||||
<string name="track_markers">Markierungen für %1$s</string>
|
<string name="track_markers">Markierungen für %1$s</string>
|
||||||
@@ -491,7 +491,7 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
|
|||||||
<string name="voice_total_distance_miles_other">Gesamtstrecke: %1$.2f Meilen</string>
|
<string name="voice_total_distance_miles_other">Gesamtstrecke: %1$.2f Meilen</string>
|
||||||
<string name="voice_total_distance_miles_two">Gesamtstrecke: 2 Meilen</string>
|
<string name="voice_total_distance_miles_two">Gesamtstrecke: 2 Meilen</string>
|
||||||
<string name="voice_total_distance_miles_zero">Gesamtstrecke: 0 Meilen</string>
|
<string name="voice_total_distance_miles_zero">Gesamtstrecke: 0 Meilen</string>
|
||||||
<string name="voice_total_distance_zero">Die Gesamtstrecke in \"Meine Tracks\" ist null.</string>
|
<string name="voice_total_distance_zero">Die Gesamtstrecke ist null.</string>
|
||||||
<!-- Waypoint Type -->
|
<!-- Waypoint Type -->
|
||||||
<string name="waypoint_type_atm">Geldautomat</string>
|
<string name="waypoint_type_atm">Geldautomat</string>
|
||||||
<string name="waypoint_type_bank">Bank</string>
|
<string name="waypoint_type_bank">Bank</string>
|
||||||
@@ -534,7 +534,7 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
|
|||||||
<string name="waypoint_type_shopping">Shopping</string>
|
<string name="waypoint_type_shopping">Shopping</string>
|
||||||
<string name="waypoint_type_sight">Sehenswürdigkeit</string>
|
<string name="waypoint_type_sight">Sehenswürdigkeit</string>
|
||||||
<string name="waypoint_type_store">Geschäft</string>
|
<string name="waypoint_type_store">Geschäft</string>
|
||||||
<string name="waypoint_type_swimming_pool">Swimmingpool</string>
|
<string name="waypoint_type_swimming_pool">Schwimmbad</string>
|
||||||
<string name="waypoint_type_telephone">Telefon</string>
|
<string name="waypoint_type_telephone">Telefon</string>
|
||||||
<string name="waypoint_type_theater">Theater</string>
|
<string name="waypoint_type_theater">Theater</string>
|
||||||
<string name="waypoint_type_toilet">Toilette</string>
|
<string name="waypoint_type_toilet">Toilette</string>
|
||||||
@@ -557,9 +557,9 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
|
|||||||
<string name="import_progress_summary_errors_msg">Fehler</string>
|
<string name="import_progress_summary_errors_msg">Fehler</string>
|
||||||
<string name="import_progress_summary_exists_msg">Schon vorhanden</string>
|
<string name="import_progress_summary_exists_msg">Schon vorhanden</string>
|
||||||
<string name="export_track_errors">Aufzeichnungen wurden nicht exportiert</string>
|
<string name="export_track_errors">Aufzeichnungen wurden nicht exportiert</string>
|
||||||
<string name="settings_default_export_uri_title">Track Export Verzeichnis</string>
|
<string name="settings_default_export_uri_title">Track-Export-Verzeichnis</string>
|
||||||
<string name="import_progress_summary_ok_msg">Dateien importiert</string>
|
<string name="import_progress_summary_ok_msg">Dateien importiert</string>
|
||||||
<string name="export_all_do_it_for_all">Für aale Konflikte übernehmen</string>
|
<string name="export_all_do_it_for_all">Für alle Konflikte übernehmen</string>
|
||||||
<string name="export_progress_summary_new_msg">Neue Tracks exportiert</string>
|
<string name="export_progress_summary_new_msg">Neue Tracks exportiert</string>
|
||||||
<string name="activity_type_escooter">E-Scooter</string>
|
<string name="activity_type_escooter">E-Scooter</string>
|
||||||
<string name="import_unsupported_format">Nicht unterstütztes Dateiformat</string>
|
<string name="import_unsupported_format">Nicht unterstütztes Dateiformat</string>
|
||||||
@@ -578,7 +578,7 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
|
|||||||
<string name="generic_overwrite">Überschreiben</string>
|
<string name="generic_overwrite">Überschreiben</string>
|
||||||
<string name="generic_skip">Überspringen</string>
|
<string name="generic_skip">Überspringen</string>
|
||||||
<string name="generic_separator_done_total">/</string>
|
<string name="generic_separator_done_total">/</string>
|
||||||
<string name="generic_show_errors">Zeige Fehler</string>
|
<string name="generic_show_errors">Fehler anzeigen</string>
|
||||||
<string name="generic_completed">Abgeschlossen</string>
|
<string name="generic_completed">Abgeschlossen</string>
|
||||||
<string name="gps_disabled_msg">GPS deaktiviert</string>
|
<string name="gps_disabled_msg">GPS deaktiviert</string>
|
||||||
<string name="export_progress_summary_overwrite_msg">Streckenaufzeichnung überschrieben</string>
|
<string name="export_progress_summary_overwrite_msg">Streckenaufzeichnung überschrieben</string>
|
||||||
@@ -589,6 +589,18 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
|
|||||||
<string name="voice_speed_lap">Rundengeschwindigkeit %1$s</string>
|
<string name="voice_speed_lap">Rundengeschwindigkeit %1$s</string>
|
||||||
<string name="voice_pace_lap">Rundenzeit %1$s</string>
|
<string name="voice_pace_lap">Rundenzeit %1$s</string>
|
||||||
<string name="toast_message_instant_track_was_exported">Letzte Aufzeichnung exportiert</string>
|
<string name="toast_message_instant_track_was_exported">Letzte Aufzeichnung exportiert</string>
|
||||||
<string name="export_track_already_exists_msg">Der Track %1$s existiert bereits im Zielverzeichnis.</string>
|
<string name="export_track_already_exists_msg">Die Streckenaufzeichung %1$s existiert bereits im Zielverzeichnis.</string>
|
||||||
<string name="gps_fixed_and_ready">GPS-Position bestimmt und bereit</string>
|
<string name="gps_fixed_and_ready">GPS-Position bestimmt und bereit</string>
|
||||||
|
<string name="instant_export_enabled_title">Sofortiger Export im Anschluss an die Übung</string>
|
||||||
|
<string name="instant_export_enabled_summary">Streckenaufzeichnung nach Beendigung der Aufnahme in den Speicher exportieren</string>
|
||||||
|
<string name="generic_open_track">Offene Strecke</string>
|
||||||
|
<string name="sensor_state_power_avg">Durchschnittliche Leistung</string>
|
||||||
|
<string name="sensor_state_cadence_avg">Durchschnittliche Trittfrequenz</string>
|
||||||
|
<string name="sensor_state_cadence_max">Maximale Trittfrequenz</string>
|
||||||
|
<string name="stats_split_interval">Geteilte Intervalle</string>
|
||||||
|
<string name="track_distance_notification">Strecke: %1$s</string>
|
||||||
|
<string name="track_speed_notification">Geschwindigkeit: %1$s</string>
|
||||||
|
<string name="track_pace_notification">Tempo: %1$s</string>
|
||||||
|
<string name="value_pace_mile">%1$d:%2$02d min/mi</string>
|
||||||
|
<string name="value_pace_kilometer">%1$d:%2$02d min/km</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -611,4 +611,5 @@ Si el dispositivo GPS reporta datos inexactos (por ejemplo, ubicación, velocida
|
|||||||
<string name="track_distance_notification">Distancia: %1$s</string>
|
<string name="track_distance_notification">Distancia: %1$s</string>
|
||||||
<string name="track_speed_notification">Velocidad: %1$s</string>
|
<string name="track_speed_notification">Velocidad: %1$s</string>
|
||||||
<string name="track_pace_notification">Ritmo: %1$s</string>
|
<string name="track_pace_notification">Ritmo: %1$s</string>
|
||||||
|
<string name="about_third_party_data">Datos de terceros</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -31,10 +31,10 @@ limitations under the License.
|
|||||||
* toimii täysin ilman verkkoyhteyttä\n
|
* toimii täysin ilman verkkoyhteyttä\n
|
||||||
* näytä jäljet kartalla (ks. alla)</string>
|
* näytä jäljet kartalla (ks. alla)</string>
|
||||||
<string name="help_track_map_title">Kuinka näen jäljen kartalla?</string>
|
<string name="help_track_map_title">Kuinka näen jäljen kartalla?</string>
|
||||||
<string name="help_track_map_content">" UsKäytä pientä karttakuvaketta.
|
<string name="help_track_map_content"> Käytä pientä karttakuvaketta.
|
||||||
\n a) (live-päivityksiä) Asenna <a href=\"https://github.com/OpenTracksApp/OSMDashboard\">OSMDashboard</a>. Se tarjoaa online-tai offline-karttoja kautta <a href=\"https://www.openstreetmap.org\">OpenStreetMap</a>.
|
\n a) (live-päivityksiä) Asenna <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Se tarjoaa online-tai offline-karttoja kautta <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||||
\n Itse asiassa, OSMDashboard on erillinen sovellus, joka vain voi käyttää (tilapäisesti) tiedot track(s), joka tulee näkyviin.
|
\n OSMDashboard on erillinen sovellus, joka voi lukea väliaikaisesti reittien dataa näyttääkseen ne.
|
||||||
\n b) Kautta tavallinen kartta sovellus (esim. <a href=\"http://osmand.net/\">OsmAnd</a> ja <a href=\"https://maps.me/\">KARTAT.MINULLE</a>). Nämä sovellukset tarvitsevat tukea KMZ-muodossa (vain paikoissa ja aikaleimat). Itse asiassa sovelluksen on tuettava < i>ACTION_VIEW< / i> MIME <i>application / vnd: n kanssa.google-maa.kmz</i>. "</string>
|
\n b) Tavallisella karttasovelluksella (esim. <a href="http://osmand.net/">OsmAnd</a> ja <a href="https://maps.me/">MAPS.ME</a>). Näiden sovellusten pitää tukea reittejä KMZ-muodossa (vain paikat ja aikaleimat). Itse asiassa sovelluksen on tuettava <i>ACTION_VIEW</i> MIME:llä <i>application/vnd.google-earth.kmz</i>. </string>
|
||||||
<string name="help_storage_pictures_title">Miten OpenTracks tallentaa merkkeihin liittyen otetut kuvat?</string>
|
<string name="help_storage_pictures_title">Miten OpenTracks tallentaa merkkeihin liittyen otetut kuvat?</string>
|
||||||
<string name="help_storage_pictures_content">Ne säilytetään sovelluksen sisäisessä tallennustilassa (eli ne näkyvät vain OpenTracksin sisällä).</string>
|
<string name="help_storage_pictures_content">Ne säilytetään sovelluksen sisäisessä tallennustilassa (eli ne näkyvät vain OpenTracksin sisällä).</string>
|
||||||
<string name="help_track_export_title">Miten vien tai jaan tallennettuja reittejä?</string>
|
<string name="help_track_export_title">Miten vien tai jaan tallennettuja reittejä?</string>
|
||||||
@@ -56,13 +56,13 @@ limitations under the License.
|
|||||||
\nJos kaikki ei auta, ilmoita virheestä. </string>
|
\nJos kaikki ei auta, ilmoita virheestä. </string>
|
||||||
<string name="help_support_title">Miten voin tukea OpenTracksin kehitystä?</string>
|
<string name="help_support_title">Miten voin tukea OpenTracksin kehitystä?</string>
|
||||||
<string name="help_support_content"> Sinä pystyt:
|
<string name="help_support_content"> Sinä pystyt:
|
||||||
\n \u0020a) levittää sanaa OpenTracksista,
|
\na) levittää sanaa OpenTracksista,
|
||||||
\n \u0020b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> ehdottaa ominaisuuksia </a>,
|
\nb) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> ehdottaa ominaisuuksia </a>,
|
||||||
\n \u0020c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> ilmoita virheistä </a>,
|
\nc) <a href="https://github.com/OpenTracksApp/OpenTracks/issues"> ilmoita virheistä </a>,
|
||||||
\n \u0020d) auttaa ohjelmistokehityksessä,
|
\nd) auttaa ohjelmistokehityksessä,
|
||||||
\n \u0020e) osta <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore"> Google Play -kaupasta </a>,
|
\ne) osta <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore"> Google Play -kaupasta </a>,
|
||||||
\n \u0020f) lahjoittaa ja
|
\nf) lahjoittaa ja
|
||||||
\n \u0020g) tee mitä tahansa mielestäsi auttaa OpenTracksia. </string>
|
\ng) tee mitä tahansa mielestäsi auttaa OpenTracksia. </string>
|
||||||
<string name="open_track_using_dashboard">Avaa OpenTracks-Dashboardilla</string>
|
<string name="open_track_using_dashboard">Avaa OpenTracks-Dashboardilla</string>
|
||||||
<string name="open_track_as_KMZ">Avaa KMZ-muodossa</string>
|
<string name="open_track_as_KMZ">Avaa KMZ-muodossa</string>
|
||||||
<string name="open_track_as_GPX">Avaa GPX-muodossa</string>
|
<string name="open_track_as_GPX">Avaa GPX-muodossa</string>
|
||||||
@@ -575,14 +575,14 @@ jotta ne voidaan tuoda kohteeseen %1$s.</string>
|
|||||||
<string name="import_progress_summary_ok_msg">Tuodut tiedostot</string>
|
<string name="import_progress_summary_ok_msg">Tuodut tiedostot</string>
|
||||||
<string name="import_progress_summary_exists_msg">Jo olemassa</string>
|
<string name="import_progress_summary_exists_msg">Jo olemassa</string>
|
||||||
<string name="import_progress_summary_errors_msg">Virhe</string>
|
<string name="import_progress_summary_errors_msg">Virhe</string>
|
||||||
<string name="generic_click_twice_cancel">Peruuta uudelleen napsauttamalla</string>
|
<string name="generic_click_twice_cancel">Paina takaisin uudestaan keskeyttääksesi</string>
|
||||||
<string name="settings_recording_fullscreen_on_while_recording_title">Kokoruutu</string>
|
<string name="settings_recording_fullscreen_on_while_recording_title">Kokoruutu</string>
|
||||||
<string name="interval_list_empty_message">Ei ole mitään intervallia vielä</string>
|
<string name="interval_list_empty_message">Ei ole mitään intervallia vielä</string>
|
||||||
<string name="export_error_post_workout">Treenin jälkeinen vienti epäonnistui, tarkista vientihakemisto.</string>
|
<string name="export_error_post_workout">Treenin jälkeinen vienti epäonnistui, tarkista vientihakemisto.</string>
|
||||||
<string name="export_progress_summary_overwrite_msg">Kappaleet ylikirjoitettu</string>
|
<string name="export_progress_summary_overwrite_msg">Kappaleet ylikirjoitettu</string>
|
||||||
<plurals name="generic_completed_with_errors">
|
<plurals name="generic_completed_with_errors">
|
||||||
<item quantity="one">Täydennetty yhdellä virheellä</item>
|
<item quantity="one">Valmista yhdellä virheellä</item>
|
||||||
<item quantity="other">Täydennetty %1$d virheillä</item>
|
<item quantity="other">Valmista %1$d virheellä</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="export_track_already_exists_msg">Kappale %1$s on jo olemassa kohdehakemistossa.</string>
|
<string name="export_track_already_exists_msg">Kappale %1$s on jo olemassa kohdehakemistossa.</string>
|
||||||
<string name="export_all_do_it_for_all">tee se kaikkien konfliktien vuoksi</string>
|
<string name="export_all_do_it_for_all">tee se kaikkien konfliktien vuoksi</string>
|
||||||
@@ -595,12 +595,12 @@ jotta ne voidaan tuoda kohteeseen %1$s.</string>
|
|||||||
<string name="export_progress_summary_new_msg">Uusia kappaleita viety</string>
|
<string name="export_progress_summary_new_msg">Uusia kappaleita viety</string>
|
||||||
<string name="export_progress_summary_skip_msg">Kappaleet ohitettu</string>
|
<string name="export_progress_summary_skip_msg">Kappaleet ohitettu</string>
|
||||||
<string name="export_progress_summary_errors_msg">Virhe</string>
|
<string name="export_progress_summary_errors_msg">Virhe</string>
|
||||||
<string name="generic_overwrite">Korvata</string>
|
<string name="generic_overwrite">Korvaa</string>
|
||||||
<string name="generic_skip">Ohittaa</string>
|
<string name="generic_skip">Ohita</string>
|
||||||
<string name="generic_separator_done_total">/</string>
|
<string name="generic_separator_done_total">/</string>
|
||||||
<string name="generic_show_errors">Näytä Virheet</string>
|
<string name="generic_show_errors">Näytä virheet</string>
|
||||||
<string name="gps_disabled_msg">GPS pois käytöstä</string>
|
<string name="gps_disabled_msg">GPS pois käytöstä</string>
|
||||||
<string name="generic_completed">Täydellinen</string>
|
<string name="generic_completed">Valmis</string>
|
||||||
<string name="sensor_state_power_avg">Avg Teho</string>
|
<string name="sensor_state_power_avg">Avg Teho</string>
|
||||||
<string name="description_altitude_loss">Korkeussijaintitappio: %1$d m (%2$d ft)</string>
|
<string name="description_altitude_loss">Korkeussijaintitappio: %1$d m (%2$d ft)</string>
|
||||||
<string name="sensor_state_heart_rate_max">Maksimisyke</string>
|
<string name="sensor_state_heart_rate_max">Maksimisyke</string>
|
||||||
@@ -611,7 +611,7 @@ jotta ne voidaan tuoda kohteeseen %1$s.</string>
|
|||||||
<string name="sensor_state_cadence_avg">Keskim.Poljinnopeus</string>
|
<string name="sensor_state_cadence_avg">Keskim.Poljinnopeus</string>
|
||||||
<string name="sensor_state_cadence_max">Suurin Poljinnopeus</string>
|
<string name="sensor_state_cadence_max">Suurin Poljinnopeus</string>
|
||||||
<string name="value_float_mile_hour">%1$.2f mt</string>
|
<string name="value_float_mile_hour">%1$.2f mt</string>
|
||||||
<string name="generic_open_track">Avaa Raita</string>
|
<string name="generic_open_track">Avattu reitti</string>
|
||||||
<string name="track_distance_notification">Etäisyys: %1$s</string>
|
<string name="track_distance_notification">Etäisyys: %1$s</string>
|
||||||
<string name="track_pace_notification">Vauhti: %1$s</string>
|
<string name="track_pace_notification">Vauhti: %1$s</string>
|
||||||
<string name="track_speed_notification">Nopeus: %1$s</string>
|
<string name="track_speed_notification">Nopeus: %1$s</string>
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
|||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
--><resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
<string name="about_preference_title">À propos d\'OpenTracks</string>
|
<string name="about_preference_title">À propos d\'OpenTracks</string>
|
||||||
<string name="about_description"><i>OpenTracks</i> est une application de suivi sportif qui respecte totalement votre vie privée.</string>
|
<string name="about_description"><i>OpenTracks</i> est une application de suivi sportif qui respecte totalement votre vie privée.</string>
|
||||||
<string name="about_url">Page web du projet : <a href="%1$s">%1$s</a></string>
|
<string name="about_url">Page web du projet : <a href="%1$s">%1$s</a></string>
|
||||||
@@ -206,7 +205,7 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
|||||||
<string name="generic_tracks">Parcours</string>
|
<string name="generic_tracks">Parcours</string>
|
||||||
<string name="generic_yes">Oui</string>
|
<string name="generic_yes">Oui</string>
|
||||||
<!-- Gps -->
|
<!-- Gps -->
|
||||||
<string name="gps_location_access">\"Données de localisation\"</string>
|
<string name="gps_location_access">« Données de localisation »</string>
|
||||||
<string name="gps_disabled">Le GPS n\'est pas activé. Appuyez ici pour accéder aux %1$s. Appuyez sur le bouton Retour pour revenir en arrière.</string>
|
<string name="gps_disabled">Le GPS n\'est pas activé. Appuyez ici pour accéder aux %1$s. Appuyez sur le bouton Retour pour revenir en arrière.</string>
|
||||||
<string name="gps_disabled_my_location">Impossible de déterminer la position actuelle. Veuillez activer d\'autres sources de localisation dans les %1$s et vous assurer
|
<string name="gps_disabled_my_location">Impossible de déterminer la position actuelle. Veuillez activer d\'autres sources de localisation dans les %1$s et vous assurer
|
||||||
que le ciel est visible. Veuillez réessayer ultérieurement.</string>
|
que le ciel est visible. Veuillez réessayer ultérieurement.</string>
|
||||||
@@ -285,7 +284,7 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
|||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
<string name="search_global_description">Parcours et repères</string>
|
<string name="search_global_description">Parcours et repères</string>
|
||||||
<string name="search_list_empty_message">Aucun parcours ni repère ne correspond à votre recherche</string>
|
<string name="search_list_empty_message">Aucun parcours ni repère ne correspond à votre recherche</string>
|
||||||
<string name="search_list_marker_track_location">Dans la piste \" %1$s \".</string>
|
<string name="search_list_marker_track_location">Dans la piste « %1$s ».</string>
|
||||||
<!-- Sensor State -->
|
<!-- Sensor State -->
|
||||||
<string name="sensor_state_battery">Niveau de la batterie</string>
|
<string name="sensor_state_battery">Niveau de la batterie</string>
|
||||||
<string name="sensor_state_cadence">Cadences</string>
|
<string name="sensor_state_cadence">Cadences</string>
|
||||||
@@ -592,7 +591,7 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
|||||||
<string name="generic_overwrite">Écraser</string>
|
<string name="generic_overwrite">Écraser</string>
|
||||||
<string name="generic_show_errors">Afficher les erreurs</string>
|
<string name="generic_show_errors">Afficher les erreurs</string>
|
||||||
<string name="generic_separator_done_total">/</string>
|
<string name="generic_separator_done_total">/</string>
|
||||||
<string name="generic_skip">Sauter</string>
|
<string name="generic_skip">Ignorer</string>
|
||||||
<string name="generic_open_track">Voie ouverte</string>
|
<string name="generic_open_track">Voie ouverte</string>
|
||||||
<string name="gps_disabled_msg">GPS désactivé</string>
|
<string name="gps_disabled_msg">GPS désactivé</string>
|
||||||
<string name="gps_fixed_and_ready">GPS fixe et prêt</string>
|
<string name="gps_fixed_and_ready">GPS fixe et prêt</string>
|
||||||
@@ -612,4 +611,5 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
|||||||
<string name="description_altitude_loss">Perte d\'altitude : %1$d m (%2$d ft)</string>
|
<string name="description_altitude_loss">Perte d\'altitude : %1$d m (%2$d ft)</string>
|
||||||
<string name="export_track_already_exists_msg">La piste %1$s existe déjà dans le répertoire de destination.</string>
|
<string name="export_track_already_exists_msg">La piste %1$s existe déjà dans le répertoire de destination.</string>
|
||||||
<string name="export_progress_summary_overwrite_msg">Pistes écrasées</string>
|
<string name="export_progress_summary_overwrite_msg">Pistes écrasées</string>
|
||||||
|
<string name="about_third_party_data">Données de tiers</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -616,4 +616,5 @@ Se te detés ou estás en interiores, non se gardarán datos do sensor (mais ser
|
|||||||
<string name="track_distance_notification">Distancia: %1$s</string>
|
<string name="track_distance_notification">Distancia: %1$s</string>
|
||||||
<string name="track_pace_notification">Ritmo: %1$s</string>
|
<string name="track_pace_notification">Ritmo: %1$s</string>
|
||||||
<string name="track_speed_notification">Velocidade: %1$s</string>
|
<string name="track_speed_notification">Velocidade: %1$s</string>
|
||||||
|
<string name="about_third_party_data">Datos de terceiros alleos</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -605,4 +605,5 @@ limitations under the License.
|
|||||||
\n * jelölések létrehozása (képekkel együtt)
|
\n * jelölések létrehozása (képekkel együtt)
|
||||||
\n * teljesen offline működik
|
\n * teljesen offline működik
|
||||||
\n * pályák megjelenítése térképen (lásd alább)</string>
|
\n * pályák megjelenítése térképen (lásd alább)</string>
|
||||||
|
<string name="about_third_party_data">Harmadik féltől származó adatok</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -196,7 +196,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
|||||||
<string name="generic_tracks">Percorsi</string>
|
<string name="generic_tracks">Percorsi</string>
|
||||||
<string name="generic_yes">Sì</string>
|
<string name="generic_yes">Sì</string>
|
||||||
<!-- Gps -->
|
<!-- Gps -->
|
||||||
<string name="gps_location_access">\"Accesso alla posizione\"</string>
|
<string name="gps_location_access">«Accesso alla posizione»</string>
|
||||||
<string name="gps_disabled">GPS non attivato. Tocca qui per andare a %1$s. Poi premi il pulsante per tornare indietro.</string>
|
<string name="gps_disabled">GPS non attivato. Tocca qui per andare a %1$s. Poi premi il pulsante per tornare indietro.</string>
|
||||||
<string name="gps_disabled_my_location">Impossibile determinare la posizione corrente. Attiva ulteriori fonti per la determinazione della posizione in %1$s e assicurati che
|
<string name="gps_disabled_my_location">Impossibile determinare la posizione corrente. Attiva ulteriori fonti per la determinazione della posizione in %1$s e assicurati che
|
||||||
il cielo sia visibile. Riprova più tardi.</string>
|
il cielo sia visibile. Riprova più tardi.</string>
|
||||||
@@ -271,7 +271,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
|||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
<string name="search_global_description">Percorsi e indicatori</string>
|
<string name="search_global_description">Percorsi e indicatori</string>
|
||||||
<string name="search_list_empty_message">Non ci sono percorsi o indicatori corrispondenti alla tua ricerca</string>
|
<string name="search_list_empty_message">Non ci sono percorsi o indicatori corrispondenti alla tua ricerca</string>
|
||||||
<string name="search_list_marker_track_location">Nel percorso \"%1$s\".</string>
|
<string name="search_list_marker_track_location">Nel percorso «%1$s».</string>
|
||||||
<!-- Sensor State -->
|
<!-- Sensor State -->
|
||||||
<string name="sensor_state_battery">Livello batteria</string>
|
<string name="sensor_state_battery">Livello batteria</string>
|
||||||
<string name="sensor_state_cadence">Cadenza</string>
|
<string name="sensor_state_cadence">Cadenza</string>
|
||||||
@@ -553,7 +553,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
|||||||
<string name="export_all_do_it_for_all">Esegui ciò per tutti i conflitti</string>
|
<string name="export_all_do_it_for_all">Esegui ciò per tutti i conflitti</string>
|
||||||
<string name="export_track_errors">Percorsi non esportati</string>
|
<string name="export_track_errors">Percorsi non esportati</string>
|
||||||
<string name="generic_overwrite">Sovrascrivere</string>
|
<string name="generic_overwrite">Sovrascrivere</string>
|
||||||
<string name="generic_skip">Saltare</string>
|
<string name="generic_skip">Ignora</string>
|
||||||
<string name="generic_separator_done_total">/</string>
|
<string name="generic_separator_done_total">/</string>
|
||||||
<string name="generic_show_errors">Mostra gli errori</string>
|
<string name="generic_show_errors">Mostra gli errori</string>
|
||||||
<string name="generic_completed">Completato</string>
|
<string name="generic_completed">Completato</string>
|
||||||
@@ -589,9 +589,9 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
|||||||
<string name="share_image_body">Penso che potresti essere interessat@ in questa immagine.</string>
|
<string name="share_image_body">Penso che potresti essere interessat@ in questa immagine.</string>
|
||||||
<string name="hold_to_stop">Tieni premuto per fermare</string>
|
<string name="hold_to_stop">Tieni premuto per fermare</string>
|
||||||
<string name="hold_to_pause">Tieni premuto per mettere in pausa</string>
|
<string name="hold_to_pause">Tieni premuto per mettere in pausa</string>
|
||||||
<string name="instant_export_enabled_summary">È stata completata l\' esportazione del percorso finito nella memoria interna del tuo dispositivo</string>
|
<string name="instant_export_enabled_summary">È stata completata l\'esportazione del percorso finito nella memoria interna del tuo dispositivo</string>
|
||||||
<string name="instant_export_enabled_title">Esportazione istantanea post-allenamento</string>
|
<string name="instant_export_enabled_title">Esportazione istantanea post-allenamento</string>
|
||||||
<string name="generic_open_track">Apri traccia</string>
|
<string name="generic_open_track">Traccia aperta</string>
|
||||||
<string name="import_prevent_reimport">È stato impostato impedisci reimportazione traccia</string>
|
<string name="import_prevent_reimport">È stato impostato impedisci reimportazione traccia</string>
|
||||||
<string name="export_error_post_workout">Esportazione post-allenamento non riuscita, controllare la directory di esportazione.</string>
|
<string name="export_error_post_workout">Esportazione post-allenamento non riuscita, controllare la directory di esportazione.</string>
|
||||||
<string name="settings_prevent_reimport_tracks_title">Impedire la reimportazione delle tracce</string>
|
<string name="settings_prevent_reimport_tracks_title">Impedire la reimportazione delle tracce</string>
|
||||||
@@ -618,4 +618,5 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
|||||||
<string name="track_distance_notification">Distanza: %1$s</string>
|
<string name="track_distance_notification">Distanza: %1$s</string>
|
||||||
<string name="track_speed_notification">Velocità: %1$s</string>
|
<string name="track_speed_notification">Velocità: %1$s</string>
|
||||||
<string name="track_pace_notification">Ritmo: %1$s</string>
|
<string name="track_pace_notification">Ritmo: %1$s</string>
|
||||||
</resources>
|
<string name="about_third_party_data">Dati di terzi</string>
|
||||||
|
</resources>
|
||||||
@@ -24,7 +24,7 @@ limitations under the License.
|
|||||||
<string name="activity_type_commercial_airplane">passasjerfly</string>
|
<string name="activity_type_commercial_airplane">passasjerfly</string>
|
||||||
<string name="activity_type_cross_country_skiing">langrenn</string>
|
<string name="activity_type_cross_country_skiing">langrenn</string>
|
||||||
<string name="activity_type_cycling">sykling</string>
|
<string name="activity_type_cycling">sykling</string>
|
||||||
<string name="activity_type_dirt_bike">dirt bike</string>
|
<string name="activity_type_dirt_bike">motocross</string>
|
||||||
<string name="activity_type_donkey_back_riding">eselridning</string>
|
<string name="activity_type_donkey_back_riding">eselridning</string>
|
||||||
<string name="activity_type_driving">kjøring</string>
|
<string name="activity_type_driving">kjøring</string>
|
||||||
<string name="activity_type_driving_bus">busskjøring</string>
|
<string name="activity_type_driving_bus">busskjøring</string>
|
||||||
@@ -606,4 +606,5 @@ limitations under the License.
|
|||||||
<string name="track_distance_notification">Distanse: %1$s</string>
|
<string name="track_distance_notification">Distanse: %1$s</string>
|
||||||
<string name="track_speed_notification">Hastighet: %1$s</string>
|
<string name="track_speed_notification">Hastighet: %1$s</string>
|
||||||
<string name="track_pace_notification">Tempo: %1$s</string>
|
<string name="track_pace_notification">Tempo: %1$s</string>
|
||||||
|
<string name="about_third_party_data">Tredjeparts data</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -610,4 +610,5 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
|||||||
<string name="track_speed_notification">Snelheid: %1$s</string>
|
<string name="track_speed_notification">Snelheid: %1$s</string>
|
||||||
<string name="track_distance_notification">Afstand: %1$s</string>
|
<string name="track_distance_notification">Afstand: %1$s</string>
|
||||||
<string name="track_pace_notification">Tempo: %1$s</string>
|
<string name="track_pace_notification">Tempo: %1$s</string>
|
||||||
|
<string name="about_third_party_data">Gegevens van derden</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -613,4 +613,5 @@ limitations under the License.
|
|||||||
<string name="value_pace_mile">%1$d:%2$02d min/mi</string>
|
<string name="value_pace_mile">%1$d:%2$02d min/mi</string>
|
||||||
<string name="value_float_mile_hour">%1$.2f mph</string>
|
<string name="value_float_mile_hour">%1$.2f mph</string>
|
||||||
<string name="value_float_kilometer_hour">%1$.2f km/h</string>
|
<string name="value_float_kilometer_hour">%1$.2f km/h</string>
|
||||||
|
<string name="about_third_party_data">Dane osób trzecich</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
<string name="image_record">Gravar</string>
|
<string name="image_record">Gravar</string>
|
||||||
<string name="image_stop">Parar</string>
|
<string name="image_stop">Parar</string>
|
||||||
<string name="image_track">Rota</string>
|
<string name="image_track">Rota</string>
|
||||||
<string name="import_error">Importação: %1$d de %2$s de %3$s</string>
|
<string name="import_error">Importado %1$d a %2$s de %3$s</string>
|
||||||
<string name="import_no_directory">%1$s não existe</string>
|
<string name="import_no_directory">%1$s não existe</string>
|
||||||
<string name="import_no_file">Nenhum arquivo para importar. Conecte o dispositivo ao computador e coloque os
|
<string name="import_no_file">Nenhum arquivo para importar. Conecte o dispositivo ao computador e coloque os
|
||||||
arquivos para importação em %1$s.</string>
|
arquivos para importação em %1$s.</string>
|
||||||
@@ -200,10 +200,10 @@
|
|||||||
<string name="search_list_marker_track_location">Na trilha \"%1$s\".</string>
|
<string name="search_list_marker_track_location">Na trilha \"%1$s\".</string>
|
||||||
<string name="sensor_state_battery">Nível da bateria</string>
|
<string name="sensor_state_battery">Nível da bateria</string>
|
||||||
<string name="sensor_state_cadence">Cadência</string>
|
<string name="sensor_state_cadence">Cadência</string>
|
||||||
<string name="sensor_state_cadence_value">%1$d rpm</string>
|
<string name="sensor_state_cadence_value">%1$d ram</string>
|
||||||
<string name="sensor_state_disconnected">Desconectado</string>
|
<string name="sensor_state_disconnected">Desconectado</string>
|
||||||
<string name="sensor_state_heart_rate">Frequência cardíaca</string>
|
<string name="sensor_state_heart_rate">Frequência cardíaca</string>
|
||||||
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
|
<string name="sensor_state_heart_rate_value">%1$d bom</string>
|
||||||
<string name="sensor_state_power">Alimentação</string>
|
<string name="sensor_state_power">Alimentação</string>
|
||||||
<string name="sensor_state_power_value">%1$d W</string>
|
<string name="sensor_state_power_value">%1$d W</string>
|
||||||
<string name="settings_not_while_recording">Não disponível durante a gravação de uma trilha</string>
|
<string name="settings_not_while_recording">Não disponível durante a gravação de uma trilha</string>
|
||||||
@@ -557,4 +557,5 @@
|
|||||||
<string name="track_distance_notification">Distância: %1$s</string>
|
<string name="track_distance_notification">Distância: %1$s</string>
|
||||||
<string name="track_speed_notification">Velocidade: %1$s</string>
|
<string name="track_speed_notification">Velocidade: %1$s</string>
|
||||||
<string name="track_pace_notification">Parcela: %1$s</string>
|
<string name="track_pace_notification">Parcela: %1$s</string>
|
||||||
|
<string name="about_third_party_data">Dados de terceiros</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -607,4 +607,5 @@ limitations under the License.
|
|||||||
<string name="track_distance_notification">Відстань: %1$s</string>
|
<string name="track_distance_notification">Відстань: %1$s</string>
|
||||||
<string name="track_speed_notification">Швидкість: %1$s</string>
|
<string name="track_speed_notification">Швидкість: %1$s</string>
|
||||||
<string name="track_pace_notification">Темп: %1$s</string>
|
<string name="track_pace_notification">Темп: %1$s</string>
|
||||||
|
<string name="about_third_party_data">Дані третіх сторін</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -231,10 +231,10 @@ limitations under the License.
|
|||||||
<!-- Sensor State -->
|
<!-- Sensor State -->
|
||||||
<string name="sensor_state_battery">Mức pin</string>
|
<string name="sensor_state_battery">Mức pin</string>
|
||||||
<string name="sensor_state_cadence">Nhịp</string>
|
<string name="sensor_state_cadence">Nhịp</string>
|
||||||
<string name="sensor_state_cadence_value">%1$d rpm</string>
|
<string name="sensor_state_cadence_value">%1$d vòng/phút</string>
|
||||||
<string name="sensor_state_disconnected">Đã ngắt kết nối</string>
|
<string name="sensor_state_disconnected">Đã ngắt kết nối</string>
|
||||||
<string name="sensor_state_heart_rate">Nhịp tim</string>
|
<string name="sensor_state_heart_rate">Nhịp tim</string>
|
||||||
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
|
<string name="sensor_state_heart_rate_value">%1$d nhịp đập/phút</string>
|
||||||
<string name="sensor_state_power">Công suất</string>
|
<string name="sensor_state_power">Công suất</string>
|
||||||
<string name="sensor_state_power_value">%1$d W</string>
|
<string name="sensor_state_power_value">%1$d W</string>
|
||||||
<string name="settings_not_while_recording">Không khả dụng khi đang ghi tuyến đường</string>
|
<string name="settings_not_while_recording">Không khả dụng khi đang ghi tuyến đường</string>
|
||||||
@@ -279,7 +279,9 @@ limitations under the License.
|
|||||||
<string name="settings_stats_units_title">Đơn vị ưa thích</string>
|
<string name="settings_stats_units_title">Đơn vị ưa thích</string>
|
||||||
<!-- Share Track -->
|
<!-- Share Track -->
|
||||||
<string name="share_track_share_file">Chia sẻ tệp tuyến đường</string>
|
<string name="share_track_share_file">Chia sẻ tệp tuyến đường</string>
|
||||||
<string name="share_track_share_file_body">Tôi nghĩ rằng bạn có thể quan tâm đến tuyến đường này.\n\nĐể xem tuyến đường này, hãy sử dụng Google Earth hoặc phần mềm vẽ bản đồ khác.\n\n%1$s</string>
|
<string name="share_track_share_file_body">Tôi nghĩ bạn có thể sẽ quan tâm đến tuyến đường này.
|
||||||
|
\n
|
||||||
|
\n%1$s</string>
|
||||||
<string name="share_track_subject">Tôi muốn chia sẻ một tuyến đường với bạn</string>
|
<string name="share_track_subject">Tôi muốn chia sẻ một tuyến đường với bạn</string>
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
<string name="stats_average_moving_pace">Nhịp độ di chuyển trung bình</string>
|
<string name="stats_average_moving_pace">Nhịp độ di chuyển trung bình</string>
|
||||||
@@ -306,7 +308,7 @@ limitations under the License.
|
|||||||
<string name="track_markers">%1$s Điểm đánh dấu</string>
|
<string name="track_markers">%1$s Điểm đánh dấu</string>
|
||||||
<!-- Track Delete -->
|
<!-- Track Delete -->
|
||||||
<string name="track_delete_one_confirm_message">Tuyến đường và các điểm đánh dấu của nó sẽ bị xóa vĩnh viễn khỏi thiết bị.</string>
|
<string name="track_delete_one_confirm_message">Tuyến đường và các điểm đánh dấu của nó sẽ bị xóa vĩnh viễn khỏi thiết bị.</string>
|
||||||
<string name="track_delete_one_confirm_title">Xóa tuyến đường</string>
|
<string name="track_delete_one_confirm_title">Xóa tuyến đường\?</string>
|
||||||
<string name="track_delete_multiple_confirm_message">Các tuyến đường được chọn và điểm đánh dấu của chúng sẽ bị xóa vĩnh viễn khỏi thiết bị.</string>
|
<string name="track_delete_multiple_confirm_message">Các tuyến đường được chọn và điểm đánh dấu của chúng sẽ bị xóa vĩnh viễn khỏi thiết bị.</string>
|
||||||
<string name="track_delete_progress_message">Đang xóa…</string>
|
<string name="track_delete_progress_message">Đang xóa…</string>
|
||||||
<!-- Track Detail -->
|
<!-- Track Detail -->
|
||||||
@@ -466,4 +468,19 @@ limitations under the License.
|
|||||||
<string name="about_version_code">Mã phiên bản: %1$d</string>
|
<string name="about_version_code">Mã phiên bản: %1$d</string>
|
||||||
<string name="about_libraries">Thư viện</string>
|
<string name="about_libraries">Thư viện</string>
|
||||||
<string name="help_feature_title">OpenTracks có những tính năng sau đây:</string>
|
<string name="help_feature_title">OpenTracks có những tính năng sau đây:</string>
|
||||||
|
<string name="help_storage_pictures_content">Chúng được lưu trữ trong bộ nhớ trong của ứng dụng (vd: chỉ truy cập được từ trong OpenTracks).</string>
|
||||||
|
<string name="help_data_google_play_services_title">OpenTracks có yêu cầu/sử dụng Dịch vụ Google Play không\?</string>
|
||||||
|
<string name="help_recording_title">OpenTracks ghi lại dữ liệu như thế nào\?</string>
|
||||||
|
<string name="about_description"><i>OpenTracks</i> là một ứng dụng theo dõi thể thao hoàn toàn tôn trọng sự riêng tư của bạn.</string>
|
||||||
|
<string name="about_url">Trang web của dự án: <a href="%1$s">%1$s</a></string>
|
||||||
|
<string name="about_history">OpenTracks là một fork của <a href="https://code.google.com/archive/p/mytracks/"><i>MyTracks của Google</i></a> (bị ngừng tiếp tục vào năm 2016).</string>
|
||||||
|
<string name="help_feature_content">* tạo các tuyến đường GPS
|
||||||
|
\n* sử dụng cảm biến nhịp tim (chỉ Bluetooth LE)
|
||||||
|
\n* tuyên bố bằng giọng nói (thời gian hoặc khoảng cách)
|
||||||
|
\n* tạo đánh dấu (bao gồm hình ảnh)
|
||||||
|
\n* hoạt động hoàn toàn ngoại tuyến
|
||||||
|
\n* hiện đường đi trên bản đồ (xem bên dưới)</string>
|
||||||
|
<string name="help_track_map_title">Hiện tuyến đường trên bản đồ như thế nào\?</string>
|
||||||
|
<string name="about_third_party_data">Dữ liệu bên thứ ba</string>
|
||||||
|
<string name="help_storage_pictures_title">OpenTracks lưu trữ các ảnh được chụp làm đánh dấu như thế nào\?</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -352,6 +352,7 @@ limitations under the License.
|
|||||||
<string name="menu_resume_track">Resume track</string>
|
<string name="menu_resume_track">Resume track</string>
|
||||||
<!-- OpenTracks -->
|
<!-- OpenTracks -->
|
||||||
<string name="recording_service">Recording service</string>
|
<string name="recording_service">Recording service</string>
|
||||||
|
<string name="delete_service">Delete service</string>
|
||||||
<!-- Permission -->
|
<!-- Permission -->
|
||||||
<string name="permission_gps_failed">OpenTracks requires permission to use GPS.</string>
|
<string name="permission_gps_failed">OpenTracks requires permission to use GPS.</string>
|
||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
@@ -475,6 +476,7 @@ limitations under the License.
|
|||||||
<string name="track_delete_one_confirm_title">Delete track?</string>
|
<string name="track_delete_one_confirm_title">Delete track?</string>
|
||||||
<string name="track_delete_multiple_confirm_message">The selected tracks and their markers will be permanently deleted from the device.</string>
|
<string name="track_delete_multiple_confirm_message">The selected tracks and their markers will be permanently deleted from the device.</string>
|
||||||
<string name="track_delete_progress_message">Deleting…</string>
|
<string name="track_delete_progress_message">Deleting…</string>
|
||||||
|
<string name="track_delete_progress">%1$d/%2$d</string>
|
||||||
<!-- Track Detail -->
|
<!-- Track Detail -->
|
||||||
<string name="track_detail_chart_tab">Chart</string>
|
<string name="track_detail_chart_tab">Chart</string>
|
||||||
<string name="track_detail_stats_tab">Stats</string>
|
<string name="track_detail_stats_tab">Stats</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user