Compare commits

...

18 Commits

Author SHA1 Message Date
Dennis Guse 45874d3749 Release: v4.0.7 2022-08-21 21:55:19 +02:00
Peter Storch 594f69320a Fix dependencies to lifecycle-viewmodel 2022-08-21 19:28:07 +02:00
dependabot[bot] d4927c9444 Bump appcompat from 1.4.2 to 1.5.0
Bumps appcompat from 1.4.2 to 1.5.0.

---
updated-dependencies:
- dependency-name: androidx.appcompat:appcompat
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-21 19:28:07 +02:00
Alexander Ivanov 00dbd056d6 Translated using Weblate (Russian)
Currently translated at 99.0% (496 of 501 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/ru/
2022-08-21 18:53:26 +02:00
M K 8f19ad8f66 Translated using Weblate (Arabic)
Currently translated at 53.6% (269 of 501 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/ar/
2022-08-21 18:53:26 +02:00
mondstern 2fdad54a7c Translated using Weblate (German)
Currently translated at 94.8% (475 of 501 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/de/
2022-08-21 18:53:26 +02:00
Peter Storch 5a5f040ed5 Fix #1340 mass track deletion time 2022-08-21 18:53:20 +02:00
Dennis Guse 0ada9a2463 Android13: request NOTIFICATION_POST permission.
Fixes #1353.
Part of #1210.
2022-08-18 08:07:37 +02:00
Dennis Guse 719bb981ec Bugfix: Public API stop does not start service in foreground.
Fixes #1354.
2022-08-17 18:43:27 +02:00
dependabot[bot] 3c95d8744d Bump mockito-android from 4.6.1 to 4.7.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 4.6.1 to 4.7.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v4.6.1...v4.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-15 07:59:40 +02:00
Dennis Guse 63d1c9ee5e GPS: set signal lost threshold to 30s. 2022-08-14 20:18:37 +02:00
Dennis Guse 1ccd2e3c0a Release: v4.0.6 2022-08-07 19:47:54 +02:00
Dennis Guse cdbddabc1b Cleanup: use ContextCompat.getColor() 2022-08-04 22:11:44 +02:00
Dennis Guse 9f6ca83f27 Bug: PublicAPI needs to call startForegroundService (SDK API26+).
Fixes #1301.
2022-08-04 22:11:44 +02:00
dependabot[bot] f2ee99a477 Bump gradle from 7.2.1 to 7.2.2
Bumps gradle from 7.2.1 to 7.2.2.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-04 06:44:02 +02:00
Dennis Guse 7009ede095 EGM2008 offset lookup for negative longitudes was wrong. 2022-08-03 17:30:31 +02:00
Dennis Guse 2b530a1c53 Cleanup: unused imports. 2022-08-02 18:44:15 +02:00
Poussinou 33673f5921 Update FUNDING.yml 2022-07-31 14:59:56 +02:00
74 changed files with 370 additions and 328 deletions
+1 -1
View File
@@ -2,4 +2,4 @@
liberapay: OpenTracks liberapay: OpenTracks
github: [rgmf, pstorch, dennisguse] github: [rgmf, pstorch, dennisguse]
custom: [ https://liberapay.com/OpenTracks, https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore ] custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
+7 -5
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.2.1' classpath 'com.android.tools.build:gradle:7.2.2'
} }
} }
@@ -59,8 +59,8 @@ android {
defaultConfig { defaultConfig {
applicationId "de.dennisguse.opentracks" applicationId "de.dennisguse.opentracks"
versionCode 4986 versionCode 5004
versionName "v4.0.5" versionName "v4.0.7"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\"" buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -120,7 +120,9 @@ android {
dependencies { dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6'
implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
implementation 'androidx.preference:preference:1.2.0' implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'com.google.android.material:material:1.6.1' implementation 'com.google.android.material:material:1.6.1'
@@ -131,7 +133,7 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'org.mockito:mockito-android:4.6.1' androidTestImplementation 'org.mockito:mockito-android:4.7.0'
androidTestUtil 'androidx.test:orchestrator:1.4.1' androidTestUtil 'androidx.test:orchestrator:1.4.1'
} }
@@ -0,0 +1,6 @@
v4.0.6: OpenTracks
Bugfix:
- EGM2008 offset computed wrong for negative longitudes
- PublicAPI crashed if OpenTracks was in background (API26+)
@@ -0,0 +1,11 @@
v4.0.7: OpenTracks
Changes:
- GPS signal lost timeout: 30s
- Android13: request permission to show notifications
- Faster deletion of tracks (vacuum database only if needed)
Bugfix:
- PublicAPI stop command resulted in crash (introduced in v4.0.6)
- Permissions: request all permissions during on startup
@@ -3,48 +3,25 @@ package de.dennisguse.opentracks;
import static androidx.test.espresso.Espresso.onData; 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.Espresso.pressBack;
import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.longClick; import static androidx.test.espresso.action.ViewActions.longClick;
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.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText; import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anything; import static org.hamcrest.Matchers.anything;
import static de.dennisguse.opentracks.util.EspressoUtils.childAtPosition;
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor; import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
import static de.dennisguse.opentracks.util.EspressoUtils.withListSize;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.Context;
import android.view.View;
import android.widget.ListView;
import androidx.test.espresso.IdlingRegistry;
import androidx.test.espresso.IdlingResource;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.assertion.ViewAssertions;
import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest; import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.GrantPermissionRule; import androidx.test.rule.GrantPermissionRule;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import de.dennisguse.opentracks.services.TrackDeleteService;
@LargeTest @LargeTest
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class EspressoDeleteTrackTest { public class EspressoDeleteTrackTest {
@@ -4,7 +4,6 @@ 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.action.ViewActions.longClick; import static androidx.test.espresso.action.ViewActions.longClick;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isClickable; import static androidx.test.espresso.matcher.ViewMatchers.isClickable;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
@@ -15,6 +15,10 @@
*/ */
package de.dennisguse.opentracks.chart; package de.dennisguse.opentracks.chart;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -25,10 +29,6 @@ import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/** /**
* Tests {@link ChartValueSeries}. * Tests {@link ChartValueSeries}.
* *
@@ -142,7 +142,7 @@ public class EGM2008UtilsTest {
} }
@Test @Test
public void data_Berlin() throws IOException { public void data_Berlin_Germany_() throws IOException {
// given // given
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0)); TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
trackPoint.setLatitude(52.530644); trackPoint.setLatitude(52.530644);
@@ -157,7 +157,7 @@ public class EGM2008UtilsTest {
} }
@Test @Test
public void data_Berlin_Caching() throws IOException { public void data_Berlin_Germany_Caching() throws IOException {
// given // given
TrackPoint trackPoint1 = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0)); TrackPoint trackPoint1 = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
trackPoint1.setLatitude(52.530644); trackPoint1.setLatitude(52.530644);
@@ -176,6 +176,22 @@ public class EGM2008UtilsTest {
assertNotEquals(altitude_egm2008.correctAltitude(trackPoint1.getLocation()), altitude_egm2008.correctAltitude(trackPoint2.getLocation()), 0.0001); assertNotEquals(altitude_egm2008.correctAltitude(trackPoint1.getLocation()), altitude_egm2008.correctAltitude(trackPoint2.getLocation()), 0.0001);
} }
@Test
public void data_Seattle_USA() throws IOException {
// given
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
trackPoint.setLatitude(47.63153);
trackPoint.setLongitude(-122.30938);
trackPoint.setAltitude(0);
// when
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
// then
assertEquals(22.99, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
}
@Test @Test
public void data_MaxUndulation() throws IOException { public void data_MaxUndulation() throws IOException {
// given // given
@@ -191,6 +207,7 @@ public class EGM2008UtilsTest {
assertEquals(-85.824, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR); assertEquals(-85.824, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
} }
@Test @Test
public void getIndices() { public void getIndices() {
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0)); TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
@@ -208,7 +225,7 @@ public class EGM2008UtilsTest {
trackPoint.setLatitude(-90); trackPoint.setLatitude(-90);
trackPoint.setLongitude(-180); trackPoint.setLongitude(-180);
assertEquals(new EGM2008Utils.Indices(2160, 0), EGM2008Utils.getIndices(trackPoint.getLocation())); assertEquals(new EGM2008Utils.Indices(2160, 2160), EGM2008Utils.getIndices(trackPoint.getLocation()));
} }
@Test @Test
+2
View File
@@ -59,6 +59,8 @@ limitations under the License.
tools:targetApi="s" /> tools:targetApi="s" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application <application
android:name=".Startup" android:name=".Startup"
android:allowBackup="false" android:allowBackup="false"
@@ -84,8 +84,8 @@ public abstract class AbstractTrackDeleteActivity extends AbstractActivity imple
/** /**
* Called every time a track is deleted. * Called every time a track is deleted.
*/ */
protected void onTrackDeleteStatus(TrackDeleteService.DeleteStatus deleteStatus) { protected void onTrackDeleteStatus(TrackDeleteService.DeletionFinishedStatus deletionFinishedStatus) {
if (deleteStatus.isFinished() && trackDeleteServiceConnection != null) { if (trackDeleteServiceConnection != null) {
trackDeleteServiceConnection.unbind(this); trackDeleteServiceConnection.unbind(this);
trackDeleteServiceConnection = null; trackDeleteServiceConnection = null;
onDeleteFinished(); onDeleteFinished();
@@ -3,19 +3,15 @@ package de.dennisguse.opentracks;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import de.dennisguse.opentracks.databinding.ActivityShowErrorBinding; import de.dennisguse.opentracks.databinding.ActivityShowErrorBinding;
import de.dennisguse.opentracks.databinding.TrackListBinding;
public class ShowErrorActivity extends AbstractActivity { public class ShowErrorActivity extends AbstractActivity {
@@ -70,7 +70,7 @@ import de.dennisguse.opentracks.ui.util.ActivityUtils;
import de.dennisguse.opentracks.ui.util.ListItemUtils; import de.dennisguse.opentracks.ui.util.ListItemUtils;
import de.dennisguse.opentracks.util.IntentDashboardUtils; import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PermissionUtils; import de.dennisguse.opentracks.util.PermissionRequester;
import de.dennisguse.opentracks.util.StringUtils; import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils; import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -241,7 +241,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
startActivity(newIntent); startActivity(newIntent);
connection.unbind(this); connection.unbind(this);
}).startAndBind(this); }).startAndBind(this, true);
}); });
viewBinding.trackListFabAction.setOnLongClickListener((view) -> { viewBinding.trackListFabAction.setOnLongClickListener((view) -> {
if (!recordingStatus.isRecording()) { if (!recordingStatus.isRecording()) {
@@ -263,13 +263,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
} }
private void requestRequiredPermissions() { private void requestRequiredPermissions() {
if (!PermissionUtils.hasGPSPermission(this)) { PermissionRequester.ALL.requestPermissionsIfNeeded(this, this, null, (requester) -> Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show());
PermissionUtils.requestGPSPermission(this, null, () -> Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show());
}
if (!PermissionUtils.hasBluetoothPermissions(this)) {
PermissionUtils.requestBluetoothPermission(this, null, null);
}
} }
@Override @Override
@@ -323,8 +317,8 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
searchView.findViewById(R.id.search_edit_frame).setPadding(0, 0, 48, 0); searchView.findViewById(R.id.search_edit_frame).setPadding(0, 0, 48, 0);
SearchView.SearchAutoComplete searchAutoComplete = searchView.findViewById(R.id.search_src_text); SearchView.SearchAutoComplete searchAutoComplete = searchView.findViewById(R.id.search_src_text);
searchAutoComplete.setHintTextColor(getResources().getColor(android.R.color.white)); searchAutoComplete.setHintTextColor(ContextCompat.getColor(this, android.R.color.white));
searchAutoComplete.setTextColor(getResources().getColor(android.R.color.white)); searchAutoComplete.setTextColor(ContextCompat.getColor(this, android.R.color.white));
searchView.setOnCloseListener(() -> { searchView.setOnCloseListener(() -> {
searchView.clearFocus(); searchView.clearFocus();
@@ -213,7 +213,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
connection.unbind(this); connection.unbind(this);
finish(); finish();
}).startAndBind(this); }).startAndBind(this, true);
return true; return true;
} }
@@ -232,9 +232,9 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
} }
@Override @Override
protected void onTrackDeleteStatus(TrackDeleteService.DeleteStatus deleteStatus) { protected void onTrackDeleteStatus(TrackDeleteService.DeletionFinishedStatus deletionFinishedStatus) {
super.onTrackDeleteStatus(deleteStatus); super.onTrackDeleteStatus(deletionFinishedStatus);
if (deleteStatus.isDeleted(trackId)) { if (deletionFinishedStatus.isDeleted(trackId)) {
runOnUiThread(this::finish); runOnUiThread(this::finish);
} }
} }
@@ -11,8 +11,6 @@ import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.Toast; import android.widget.Toast;
@@ -49,7 +47,6 @@ import de.dennisguse.opentracks.ui.markers.MarkerEditActivity;
import de.dennisguse.opentracks.ui.markers.MarkerListActivity; import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
import de.dennisguse.opentracks.util.IntentDashboardUtils; import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PermissionUtils;
import de.dennisguse.opentracks.util.TrackIconUtils; import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackUtils; import de.dennisguse.opentracks.util.TrackUtils;
@@ -138,7 +135,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
viewBinding.trackRecordingFabAction.setImageResource(R.drawable.ic_baseline_stop_24); viewBinding.trackRecordingFabAction.setImageResource(R.drawable.ic_baseline_stop_24);
viewBinding.trackRecordingFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.opentracks_secondary_color)); viewBinding.trackRecordingFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.opentracks_secondary_color));
viewBinding.trackRecordingFabAction.setBackgroundColor(getResources().getColor(R.color.opentracks_secondary_color)); viewBinding.trackRecordingFabAction.setBackgroundColor(ContextCompat.getColor(this, R.color.opentracks_secondary_color));
viewBinding.trackRecordingFabAction.setOnLongClickListener((view) -> { viewBinding.trackRecordingFabAction.setOnLongClickListener((view) -> {
((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(1000); ((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(1000);
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this); trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this);
@@ -145,7 +145,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
connection.unbind(this); connection.unbind(this);
finish(); finish();
}).startAndBind(this); }).startAndBind(this, true);
} }
@Override @Override
@@ -32,9 +32,7 @@ import java.util.List;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.TrackDataHub; import de.dennisguse.opentracks.data.TrackDataHub;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.databinding.ChartBinding; import de.dennisguse.opentracks.databinding.ChartBinding;
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.chart;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
@@ -24,6 +24,7 @@ import android.graphics.Paint.Style;
import android.graphics.Path; import android.graphics.Path;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import java.text.NumberFormat; import java.text.NumberFormat;
@@ -68,12 +69,12 @@ abstract class ChartValueSeries {
fillPaint = new Paint(); fillPaint = new Paint();
fillPaint.setStyle(Style.FILL); fillPaint.setStyle(Style.FILL);
fillPaint.setColor(context.getResources().getColor(fillColor)); fillPaint.setColor(ContextCompat.getColor(context, fillColor));
fillPaint.setAntiAlias(true); fillPaint.setAntiAlias(true);
strokePaint = new Paint(); strokePaint = new Paint();
strokePaint.setStyle(Style.STROKE); strokePaint.setStyle(Style.STROKE);
strokePaint.setColor(context.getResources().getColor(strokeColor)); strokePaint.setColor(ContextCompat.getColor(context, strokeColor));
strokePaint.setAntiAlias(true); strokePaint.setAntiAlias(true);
// Make copies of the stroke paint with the default thickness // Make copies of the stroke paint with the default thickness
@@ -187,7 +187,6 @@ public class ContentProviderUtils {
FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId)); FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
} }
// Delete track last since it triggers a database vacuum call
String whereClause = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?"))); String whereClause = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?")));
contentResolver.delete(TracksColumns.CONTENT_URI, whereClause, trackIds.stream().map(id -> Long.toString(id.getId())).toArray(String[]::new)); contentResolver.delete(TracksColumns.CONTENT_URI, whereClause, trackIds.stream().map(id -> Long.toString(id.getId())).toArray(String[]::new));
} }
@@ -195,8 +194,6 @@ public class ContentProviderUtils {
public void deleteTrack(Context context, @NonNull Track.Id trackId) { public void deleteTrack(Context context, @NonNull Track.Id trackId) {
// Delete track folder resources. // Delete track folder resources.
FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId)); FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
// Delete track last since it triggers a database vacuum call
contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())}); contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())});
} }
@@ -27,6 +27,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteQueryBuilder; import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
@@ -39,6 +40,7 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.tables.MarkerColumns; import de.dennisguse.opentracks.data.tables.MarkerColumns;
import de.dennisguse.opentracks.data.tables.TrackPointsColumns; import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
import de.dennisguse.opentracks.data.tables.TracksColumns; import de.dennisguse.opentracks.data.tables.TracksColumns;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/** /**
* A {@link ContentProvider} that handles access to track points, tracks, and markers tables. * A {@link ContentProvider} that handles access to track points, tracks, and markers tables.
@@ -53,6 +55,8 @@ public class CustomContentProvider extends ContentProvider {
private static final String SQL_LIST_DELIMITER = ","; private static final String SQL_LIST_DELIMITER = ",";
private static final int TOTAL_DELETED_ROWS_VACUUM_THRESHOLD = 10000;
private final UriMatcher uriMatcher; private final UriMatcher uriMatcher;
private SQLiteDatabase db; private SQLiteDatabase db;
@@ -131,14 +135,12 @@ public class CustomContentProvider extends ContentProvider {
@Override @Override
public int delete(@NonNull Uri url, String where, String[] selectionArgs) { public int delete(@NonNull Uri url, String where, String[] selectionArgs) {
String table; String table;
boolean shouldVacuum = false;
switch (getUrlType(url)) { switch (getUrlType(url)) {
case TRACKPOINTS: case TRACKPOINTS:
table = TrackPointsColumns.TABLE_NAME; table = TrackPointsColumns.TABLE_NAME;
break; break;
case TRACKS: case TRACKS:
table = TracksColumns.TABLE_NAME; table = TracksColumns.TABLE_NAME;
shouldVacuum = true;
break; break;
case MARKERS: case MARKERS:
table = MarkerColumns.TABLE_NAME; table = MarkerColumns.TABLE_NAME;
@@ -147,23 +149,40 @@ public class CustomContentProvider extends ContentProvider {
throw new IllegalArgumentException("Unknown URL " + url); throw new IllegalArgumentException("Unknown URL " + url);
} }
Log.w(TAG, "Deleting table " + table); Log.w(TAG, "Deleting from table " + table);
int count; int totalChangesBefore = getTotalChanges();
int deletedRowsFromTable;
try { try {
db.beginTransaction(); db.beginTransaction();
count = db.delete(table, where, selectionArgs); deletedRowsFromTable = db.delete(table, where, selectionArgs);
Log.i(TAG, "Deleted " + deletedRowsFromTable + " rows of table " + table);
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();
} }
getContext().getContentResolver().notifyChange(url, null, false); getContext().getContentResolver().notifyChange(url, null, false);
if (shouldVacuum) { int totalChanges = getTotalChanges() - totalChangesBefore;
// If a potentially large amount of data was deleted, reclaim its space. Log.i(TAG, "Deleted " + totalChanges + " total rows from database");
Log.i(TAG, "Vacuuming the database.");
PreferencesUtils.addTotalRowsDeleted(totalChanges);
int totalRowsDeleted = PreferencesUtils.getTotalRowsDeleted();
if (totalRowsDeleted > TOTAL_DELETED_ROWS_VACUUM_THRESHOLD) {
Log.i(TAG, "TotalRowsDeleted " + totalRowsDeleted + ", starting to vacuum the database.");
db.execSQL("VACUUM"); db.execSQL("VACUUM");
PreferencesUtils.resetTotalRowsDeleted();
} }
return count;
return deletedRowsFromTable;
}
private int getTotalChanges() {
int totalCount;
try (Cursor cursor = db.rawQuery("SELECT total_changes()", null)) {
cursor.moveToNext();
totalCount = cursor.getInt(0);
}
return totalCount;
} }
@Override @Override
@@ -32,7 +32,6 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.tables.MarkerColumns; import de.dennisguse.opentracks.data.tables.MarkerColumns;
@@ -17,8 +17,8 @@ import androidx.documentfile.provider.DocumentFile;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.io.file.exporter.ExportActivity; import de.dennisguse.opentracks.io.file.exporter.ExportActivity;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.IntentUtils;
public abstract class DirectoryChooserActivity extends AppCompatActivity { public abstract class DirectoryChooserActivity extends AppCompatActivity {
@@ -25,6 +25,7 @@ import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater; import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.ui.markers.MarkerUtils; import de.dennisguse.opentracks.ui.markers.MarkerUtils;
@@ -290,4 +291,5 @@ public class TrackImporter {
public void cleanImport() { public void cleanImport() {
contentProviderUtils.deleteTracks(context, trackIds); contentProviderUtils.deleteTracks(context, trackIds);
} }
} }
@@ -34,7 +34,7 @@ public abstract class AbstractAPIActivity extends AppCompatActivity {
if (PreferencesUtils.isPublicAPIenabled()) { if (PreferencesUtils.isPublicAPIenabled()) {
Log.i(TAG, "Received and trying to execute requested action."); Log.i(TAG, "Received and trying to execute requested action.");
new TrackRecordingServiceConnection(serviceConnectedCallback) new TrackRecordingServiceConnection(serviceConnectedCallback)
.startAndBind(this); .startAndBind(this, isStartServiceForeground());
} else { } else {
Toast.makeText(this, getString(R.string.settings_public_api_disabled_toast), Toast.LENGTH_LONG).show(); Toast.makeText(this, getString(R.string.settings_public_api_disabled_toast), Toast.LENGTH_LONG).show();
Log.w(TAG, "Public API is disabled; ignoring request."); Log.w(TAG, "Public API is disabled; ignoring request.");
@@ -42,6 +42,10 @@ public abstract class AbstractAPIActivity extends AppCompatActivity {
} }
} }
protected boolean isStartServiceForeground() {
return false;
}
protected abstract void execute(TrackRecordingService service); protected abstract void execute(TrackRecordingService service);
protected abstract boolean isPostExecuteStopService(); protected abstract boolean isPostExecuteStopService();
@@ -57,4 +57,9 @@ public class StartRecording extends AbstractAPIActivity {
protected boolean isPostExecuteStopService() { protected boolean isPostExecuteStopService() {
return false; return false;
} }
@Override
protected boolean isStartServiceForeground() {
return true;
}
} }
@@ -35,7 +35,7 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorDataCycling;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning; import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet; import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.PermissionUtils; import de.dennisguse.opentracks.util.PermissionRequester;
/** /**
* Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors. * Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors.
@@ -162,7 +162,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
} else { } else {
connectionManager.disconnect(); connectionManager.disconnect();
} }
if (!PermissionUtils.hasBluetoothPermissions(context)) { if (!PermissionRequester.BLUETOOTH.hasPermission(context)) {
Log.w(TAG, "BLUETOOTH_SCAN and/or BLUETOOTH_CONNECT not granted; not connecting."); Log.w(TAG, "BLUETOOTH_SCAN and/or BLUETOOTH_CONNECT not granted; not connecting.");
} }
@@ -15,12 +15,14 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class TrackDeleteService extends Service { public class TrackDeleteService extends Service {
@@ -31,7 +33,7 @@ public class TrackDeleteService extends Service {
private final Binder binder = new Binder(); private final Binder binder = new Binder();
private ExecutorService serviceExecutor; private ExecutorService serviceExecutor;
private MutableLiveData<DeleteStatus> deleteResultObservable; private MutableLiveData<DeletionFinishedStatus> deleteResultObservable;
private NotificationManager notificationManager; private NotificationManager notificationManager;
private NotificationCompat.Builder notificationBuilder; private NotificationCompat.Builder notificationBuilder;
@@ -59,22 +61,18 @@ public class TrackDeleteService extends Service {
return START_NOT_STICKY; return START_NOT_STICKY;
} }
private void deleteTracks(@NonNull ArrayList<Track.Id> trackIds) { private void deleteTracks(@NonNull List<Track.Id> trackIds) {
sendResult(null,0, trackIds.size());
serviceExecutor.execute(() -> { serviceExecutor.execute(() -> {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
for (int i = 0; i < trackIds.size(); i++) { contentProviderUtils.deleteTracks(this, trackIds);
updateNotification(i + 1, trackIds.size()); sendResult(trackIds);
contentProviderUtils.deleteTrack(this, trackIds.get(i));
sendResult(trackIds.get(i), i + 1, trackIds.size());
}
stopSelf(); stopSelf();
}); });
} }
private void sendResult(Track.Id trackId, int deletes, int total) { private void sendResult(List<Track.Id> trackIds) {
if (deleteResultObservable != null) { if (deleteResultObservable != null) {
deleteResultObservable.postValue(new DeleteStatus(trackId, deletes, total)); deleteResultObservable.postValue(new DeletionFinishedStatus(trackIds));
} }
} }
@@ -83,7 +81,7 @@ public class TrackDeleteService extends Service {
return binder; return binder;
} }
public LiveData<DeleteStatus> getDeletingStatusObservable() { public LiveData<DeletionFinishedStatus> getDeletingStatusObservable() {
return deleteResultObservable; return deleteResultObservable;
} }
@@ -106,25 +104,13 @@ public class TrackDeleteService extends Service {
notificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID); notificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
notificationBuilder notificationBuilder
.setContentTitle(this.getString(R.string.track_delete_progress_message)) .setContentTitle(this.getString(R.string.track_delete_progress_message))
.setContentText(this.getString(R.string.track_delete_progress, 0, tracksToDelete)) .setContentText(this.getString(R.string.track_delete_number_of_tracks, tracksToDelete))
.setSmallIcon(R.drawable.ic_logo_color_24dp) .setSmallIcon(R.drawable.ic_logo_color_24dp)
.setProgress(tracksToDelete, 0, false); .setProgress(0, 0, true);
startForeground(NOTIFICATION_ID, notificationBuilder.build()); 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 { public class Binder extends android.os.Binder {
private Binder() { private Binder() {
@@ -136,36 +122,25 @@ public class TrackDeleteService extends Service {
} }
} }
public static class DeleteStatus { public static class DeletionFinishedStatus {
private final int progress; private final List<Track.Id> trackIds;
private final int max;
private final Track.Id trackId;
/** /**
* @param trackId Track.Id just deleted if any. * @param trackIds List of deleted Track.Ids.
* @param progress number of deletes.
* @param max total of deletes to be done.
*/ */
private DeleteStatus(@Nullable Track.Id trackId, int progress, int max) { private DeletionFinishedStatus(@Nullable List<Track.Id> trackIds) {
this.trackId = trackId; this.trackIds = trackIds;
this.progress = progress;
this.max = max;
}
public boolean isFinished() {
return progress == max;
} }
public boolean isDeleted(Track.Id trackId) { public boolean isDeleted(Track.Id trackId) {
return this.trackId != null && this.trackId.equals(trackId); return this.trackIds != null && this.trackIds.contains(trackId);
} }
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return "DeleteStatus{" + return "DeleteStatus{" +
"number of deletes=" + progress + "trackIds=" + trackIds +
", total=" + max +
'}'; '}';
} }
} }
@@ -129,7 +129,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
trackRecordingManager = null; trackRecordingManager = null;
// Reverse order from onCreate // Reverse order from onCreate
showNotification(false); //TODO Why? stopForeground(true);
notificationManager.stop(); notificationManager.stop();
notificationManager = null; notificationManager = null;
@@ -205,7 +206,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
private void startSensors() { private void startSensors() {
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock); wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
trackPointCreator.start(this, handler); trackPointCreator.start(this, handler);
showNotification(true); startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this));
} }
public void endCurrentTrack() { public void endCurrentTrack() {
@@ -247,7 +248,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
if (!isRecording()) return; if (!isRecording()) return;
trackPointCreator.stop(); trackPointCreator.stop();
showNotification(false); stopForeground(true);
notificationManager.cancelNotification();
wakeLock = SystemUtils.releaseWakeLock(wakeLock); wakeLock = SystemUtils.releaseWakeLock(wakeLock);
} }
@@ -273,19 +275,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
} }
} }
private void showNotification(boolean isGpsStarted) {
if (isRecording()) {
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setRecording(this, recordingStatus.getTrackId()));
}
if (!isRecording() && isGpsStarted) {
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this));
}
if (!isRecording() && !isGpsStarted) {
stopForeground(true);
notificationManager.cancelNotification();
}
}
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) { public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
if (!isRecording()) { if (!isRecording()) {
return null; return null;
@@ -28,6 +28,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import de.dennisguse.opentracks.BuildConfig; import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
@@ -66,15 +67,21 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
/** /**
* Starts and binds the service. * Starts and binds the service.
*
* @param foreground is the service expected to call `startForeground()`?
*/ */
public void startAndBind(Context context) { public void startAndBind(Context context, boolean foreground) {
if (trackRecordingService != null) { if (trackRecordingService != null) {
// Service is already started and bound. // Service is already started and bound.
return; return;
} }
Log.i(TAG, "Starting the service."); Log.i(TAG, "Starting the service.");
context.startService(new Intent(context, TrackRecordingService.class)); if (foreground) {
ContextCompat.startForegroundService(context, new Intent(context, TrackRecordingService.class));
} else {
context.startService(new Intent(context, TrackRecordingService.class));
}
startConnection(context); startConnection(context);
} }
@@ -88,7 +95,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
@Deprecated @Deprecated
public void startAndBindWithCallback(Context context) { public void startAndBindWithCallback(Context context) {
if (trackRecordingService == null) { if (trackRecordingService == null) {
startAndBind(context); startAndBind(context, false);
return; return;
} }
if (callback != null) { if (callback != null) {
@@ -68,6 +68,7 @@ class TrackRecordingServiceNotificationManager implements SharedPreferences.OnSh
} }
void stop() { void stop() {
cancelNotification();
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(this); PreferencesUtils.unregisterOnSharedPreferenceChangeListener(this);
} }
@@ -5,8 +5,8 @@ import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnno
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceAverageSpeedPace; import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceAverageSpeedPace;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapHeartRate; import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapHeartRate;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapSpeedPace; import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapSpeedPace;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceTotalDistance;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceMovingTime; import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceMovingTime;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceTotalDistance;
import android.content.Context; import android.content.Context;
import android.text.Spannable; import android.text.Spannable;
@@ -19,7 +19,7 @@ import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.LocationUtils; import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PermissionUtils; import de.dennisguse.opentracks.util.PermissionRequester;
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener { public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
@@ -54,7 +54,7 @@ public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener
//TODO upgrade to AGP7.0.0/API31 started complaining about removeUpdates. //TODO upgrade to AGP7.0.0/API31 started complaining about removeUpdates.
public void onStop() { public void onStop() {
if (locationManager != null && context != null) { if (locationManager != null && context != null) {
if (PermissionUtils.hasGPSPermission(context)) { if (PermissionRequester.GPS.hasPermission(context)) {
locationManager.removeUpdates(this); locationManager.removeUpdates(this);
} }
locationManager = null; locationManager = null;
@@ -152,7 +152,7 @@ public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener
Log.e(TAG, "Not started."); Log.e(TAG, "Not started.");
return; return;
} }
if (PermissionUtils.hasGPSPermission(context)) { if (PermissionRequester.GPS.hasPermission(context)) {
try { try {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, gpsInterval.toMillis(), 0, this); locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, gpsInterval.toMillis(), 0, this);
} catch (SecurityException e) { } catch (SecurityException e) {
@@ -23,7 +23,7 @@ class GpsStatus {
private static final String TAG = GpsStatus.class.getSimpleName(); private static final String TAG = GpsStatus.class.getSimpleName();
// The duration that GpsStatus waits from minimal interval to consider GPS lost. // The duration that GpsStatus waits from minimal interval to consider GPS lost.
private static final Duration SIGNAL_LOST_THRESHOLD = Duration.ofSeconds(10); private static final Duration SIGNAL_LOST_THRESHOLD = Duration.ofSeconds(30);
private Distance thresholdHorizontalAccuracy; private Distance thresholdHorizontalAccuracy;
// Threshold for time without points. // Threshold for time without points.
@@ -8,9 +8,6 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.services.RecordingStatus;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
public class MainSettingsFragment extends PreferenceFragmentCompat { public class MainSettingsFragment extends PreferenceFragmentCompat {
@@ -826,4 +826,17 @@ public class PreferencesUtils {
return getString(R.string.show_on_map_format_key, IntentDashboardUtils.PREFERENCE_ID_ASK); return getString(R.string.show_on_map_format_key, IntentDashboardUtils.PREFERENCE_ID_ASK);
} }
public static int getTotalRowsDeleted() {
return getInt(R.string.total_rows_deleted_key, 0);
}
public static void addTotalRowsDeleted(final int totalRowsDeletedToAdd) {
int newTotalRowsDeleted = getTotalRowsDeleted() + totalRowsDeletedToAdd;
setInt(R.string.total_rows_deleted_key, newTotalRowsDeleted);
}
public static void resetTotalRowsDeleted() {
setInt(R.string.total_rows_deleted_key, 0);
}
} }
@@ -11,7 +11,6 @@ import androidx.preference.PreferenceFragmentCompat;
import de.dennisguse.opentracks.AbstractActivity; import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.databinding.SettingsBinding; import de.dennisguse.opentracks.databinding.SettingsBinding;
import de.dennisguse.opentracks.databinding.TrackRecordedBinding;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
public class SettingsActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller { public class SettingsActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller {
@@ -29,7 +29,7 @@ import java.util.stream.Collectors;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.sensors.BluetoothUtils; import de.dennisguse.opentracks.sensors.BluetoothUtils;
import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.PermissionUtils; import de.dennisguse.opentracks.util.PermissionRequester;
/** /**
* Preference to select a discoverable Bluetooth LE device. * Preference to select a discoverable Bluetooth LE device.
@@ -145,20 +145,18 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
bluetoothIcon = AnimatedVectorDrawableCompat.create(getContext(), R.drawable.ic_bluetooth_searching_animated_24dp); bluetoothIcon = AnimatedVectorDrawableCompat.create(getContext(), R.drawable.ic_bluetooth_searching_animated_24dp);
bluetoothIcon.start(); bluetoothIcon.start();
if (!PermissionUtils.hasBluetoothPermissions(getContext())) { PermissionRequester.BLUETOOTH.requestPermissionsIfNeeded(getContext(), this,
PermissionUtils.requestBluetoothPermission(this, this::startBluetoothScan,
this::startBluetoothScan, (requester) -> {
() -> { if (requester.shouldShowRequestPermissionRationale(this)) {
if (PermissionUtils.shouldShowRequestPermissionRationaleBluetooth(this)) { Toast.makeText(getContext(), R.string.permission_bluetooth_failed_rejected, Toast.LENGTH_LONG).show();
Toast.makeText(getContext(), R.string.permission_bluetooth_failed_rejected, Toast.LENGTH_LONG).show(); } else {
} else { Toast.makeText(getContext(), R.string.permission_bluetooth_failed, Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), R.string.permission_bluetooth_failed, Toast.LENGTH_SHORT).show(); }
} dismiss();
dismiss(); });
});
} else { startBluetoothScan();
startBluetoothScan();
}
} }
private void startBluetoothScan() { private void startBluetoothScan() {
@@ -5,7 +5,6 @@ import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.DatePicker; import android.widget.DatePicker;
@@ -22,6 +22,8 @@ import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.core.content.ContextCompat;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@@ -169,7 +171,7 @@ public class ListItemUtils {
textView.setVisibility(View.VISIBLE); textView.setVisibility(View.VISIBLE);
textView.setText(value); textView.setText(value);
if (addShadow) { if (addShadow) {
textView.setShadowLayer(5, 0, 2, context.getResources().getColor(android.R.color.black)); textView.setShadowLayer(5, 0, 2, ContextCompat.getColor(context, android.R.color.black));
} else { } else {
textView.setShadowLayer(0, 0, 0, 0); textView.setShadowLayer(0, 0, 0, 0);
} }
@@ -27,11 +27,11 @@ abstract public class RecyclerViewSwipeDeleteCallback extends ItemTouchHelper.Ca
public RecyclerViewSwipeDeleteCallback(Context context) { public RecyclerViewSwipeDeleteCallback(Context context) {
background = new ColorDrawable(); background = new ColorDrawable();
backgroundColor = context.getResources().getColor(R.color.colorBackgroundDelete); backgroundColor = ContextCompat.getColor(context, R.color.colorBackgroundDelete);
clearPaint = new Paint(); clearPaint = new Paint();
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
deleteDrawable = ContextCompat.getDrawable(context, R.drawable.ic_delete_forever_24dp); deleteDrawable = ContextCompat.getDrawable(context, R.drawable.ic_delete_forever_24dp);
deleteDrawable.setTint(context.getResources().getColor(R.color.colorForegroundDelete)); deleteDrawable.setTint(ContextCompat.getColor(context, R.color.colorForegroundDelete));
intrinsicWidth = deleteDrawable.getIntrinsicWidth(); intrinsicWidth = deleteDrawable.getIntrinsicWidth();
intrinsicHeight = deleteDrawable.getIntrinsicHeight(); intrinsicHeight = deleteDrawable.getIntrinsicHeight();
} }
@@ -65,7 +65,7 @@ public class EGM2008Utils {
if (location.getLongitude() >= 0) { if (location.getLongitude() >= 0) {
longitude = location.getLongitude(); longitude = location.getLongitude();
} else { } else {
longitude = 180 + Math.abs(location.getLongitude()); longitude = 360 + location.getLongitude();
} }
int longitudeIndex = (int) (longitude * RESOLUTION_IN_MINUTES); int longitudeIndex = (int) (longitude * RESOLUTION_IN_MINUTES);
@@ -172,5 +172,13 @@ public class EGM2008Utils {
public int hashCode() { public int hashCode() {
return Objects.hash(latitudeIndex, longitudeIndex); return Objects.hash(latitudeIndex, longitudeIndex);
} }
@Override
public String toString() {
return "Indices{" +
"latitudeIndex=" + latitudeIndex +
", longitudeIndex=" + longitudeIndex +
'}';
}
} }
} }
@@ -17,8 +17,6 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
@@ -0,0 +1,101 @@
package de.dennisguse.opentracks.util;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.activity.result.ActivityResultCaller;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PermissionRequester {
private final List<String> permissions;
public PermissionRequester(List<String> permissions) {
this.permissions = permissions;
}
public boolean hasPermission(Context context) {
return permissions.stream()
.map(p -> ContextCompat.checkSelfPermission(context, p))
.allMatch(r -> r == PackageManager.PERMISSION_GRANTED);
}
public void requestPermissionsIfNeeded(Context context, ActivityResultCaller caller, @Nullable Runnable onGranted, @Nullable RejectedCallback onRejected) {
if (!hasPermission(context)) {
requestPermission(caller, onGranted, onRejected);
}
}
public boolean shouldShowRequestPermissionRationale(Fragment context) {
return permissions.stream()
.anyMatch(context::shouldShowRequestPermissionRationale);
}
private void requestPermission(ActivityResultCaller context, @Nullable Runnable onGranted, @Nullable RejectedCallback onRejected) {
ActivityResultLauncher<String[]> locationPermissionRequest = context.registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
boolean isGranted = permissions.stream()
.allMatch(p -> result.getOrDefault(p, false));
if (isGranted && onGranted != null) {
onGranted.run();
}
if (!isGranted && onRejected != null) {
onRejected.rejected(this);
}
}
);
locationPermissionRequest.launch(permissions.toArray(permissions.toArray(new String[0])));
}
private static final List<String> GPS_PERMISSION = List.of(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION);
private static final List<String> BLUETOOTH_PERMISSIONS;
static {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
BLUETOOTH_PERMISSIONS = List.of(Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT);
} else {
BLUETOOTH_PERMISSIONS = Collections.emptyList();
}
}
private static final List<String> NOTIFICATION_PERMISSIONS;
static {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
NOTIFICATION_PERMISSIONS = List.of(Manifest.permission.POST_NOTIFICATIONS);
} else {
NOTIFICATION_PERMISSIONS = Collections.emptyList();
}
}
private static final List<String> ALL_PERMISSIONS;
static {
ArrayList<String> all = new ArrayList<>(GPS_PERMISSION);
all.addAll(BLUETOOTH_PERMISSIONS);
all.addAll(NOTIFICATION_PERMISSIONS);
ALL_PERMISSIONS = Collections.unmodifiableList(all);
}
public final static PermissionRequester GPS = new PermissionRequester(GPS_PERMISSION);
public final static PermissionRequester BLUETOOTH = new PermissionRequester(BLUETOOTH_PERMISSIONS);
public final static PermissionRequester NOTIFICATION = new PermissionRequester(NOTIFICATION_PERMISSIONS);
public final static PermissionRequester ALL = new PermissionRequester(ALL_PERMISSIONS);
public interface RejectedCallback {
void rejected(PermissionRequester permissionRequester);
}
}
@@ -1,74 +0,0 @@
package de.dennisguse.opentracks.util;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.activity.result.ActivityResultCaller;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import java.util.Arrays;
public class PermissionUtils {
private static final String[] GPS = new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION};
private static final String[] BLUETOOTH;
static {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
BLUETOOTH = new String[]{};
} else {
BLUETOOTH = new String[]{Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT};
}
}
private PermissionUtils() {
}
public static boolean hasGPSPermission(Context context) {
return hasPermissions(context, GPS);
}
public static void requestGPSPermission(ActivityResultCaller context, @Nullable Runnable onGranted, @Nullable Runnable onRejected) {
requestPermission(context, GPS, onGranted, onRejected);
}
public static boolean shouldShowRequestPermissionRationaleBluetooth(Fragment context) {
return Arrays.stream(BLUETOOTH).anyMatch(context::shouldShowRequestPermissionRationale);
}
public static boolean hasBluetoothPermissions(Context context) {
return hasPermissions(context, BLUETOOTH);
}
public static void requestBluetoothPermission(ActivityResultCaller context, @Nullable Runnable onGranted, @Nullable Runnable onRejected) {
requestPermission(context, BLUETOOTH, onGranted, onRejected);
}
private static void requestPermission(ActivityResultCaller context, final String[] permissions, @Nullable Runnable onGranted, @Nullable Runnable onRejected) {
ActivityResultLauncher<String[]> locationPermissionRequest = context.registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
boolean isGranted = Arrays.stream(permissions)
.allMatch(p -> result.getOrDefault(p, false));
if (isGranted && onGranted != null) {
onGranted.run();
}
if (!isGranted && onRejected != null) {
onRejected.run();
}
}
);
locationPermissionRequest.launch(permissions);
}
private static boolean hasPermissions(Context context, String[] permissions) {
return Arrays.stream(permissions)
.map(p -> ContextCompat.checkSelfPermission(context, p))
.allMatch(r -> r == PackageManager.PERMISSION_GRANTED);
}
}
+7 -6
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc. Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,8 +13,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 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>
<resources>
<!-- Activity Type --> <!-- Activity Type -->
<string name="activity_type_airplane">طائرة</string> <string name="activity_type_airplane">طائرة</string>
<string name="activity_type_atv">ATV</string> <string name="activity_type_atv">ATV</string>
@@ -200,14 +200,15 @@ limitations under the License.
<string name="settings_stats_units_imperial">وحدات القياس البريطانية (ميل، قدم)</string> <string name="settings_stats_units_imperial">وحدات القياس البريطانية (ميل، قدم)</string>
<string name="settings_stats_units_metric">وحدات القياس المترية (كم، م)</string> <string name="settings_stats_units_metric">وحدات القياس المترية (كم، م)</string>
<string name="settings_stats_units_title">الوحدات المفضلة</string> <string name="settings_stats_units_title">الوحدات المفضلة</string>
<!-- Share Track --> <!-- Share Track -->
<string name="share_track_share_file_body">أعتقد أنك قد تكون مهتمًا بهذا المسار.\n\nلعرض هذا المسار، استخدام برنامج Google Earth أو غيره من البرامج تصميم الخرائط.\n\n%1$s</string> <string name="share_track_share_file_body">أعتقد أنك قد تكون مهتمًا بهذا المسار.
\n
\n%1$s</string>
<string name="share_track_subject">أريد مشاركة مسار معك</string> <string name="share_track_subject">أريد مشاركة مسار معك</string>
<!-- Stats --> <!-- Stats -->
<string name="stats_average_moving_pace">متوسط إيقاع التحرك</string> <string name="stats_average_moving_pace">متوسط إيقاع التحرك</string>
<string name="stats_average_moving_speed">متوسط سرعة التحرك</string> <string name="stats_average_moving_speed">متوسط سرعة التحرك</string>
<string name="stats_average_pace">متوسط ​​الإيقاع</string> <string name="stats_average_pace">متوسط الإيقاع</string>
<string name="stats_average_speed">متوسط السرعة</string> <string name="stats_average_speed">متوسط السرعة</string>
<string name="stats_altitude">الارتفاع</string> <string name="stats_altitude">الارتفاع</string>
<string name="stats_fastest_pace">أسرع إيقاع</string> <string name="stats_fastest_pace">أسرع إيقاع</string>
-1
View File
@@ -411,7 +411,6 @@
<string name="stats_split_interval">Intervalo partido</string> <string name="stats_split_interval">Intervalo partido</string>
<string name="track_recording_notification_accuracy">Precisión de ubicación: %1$s</string> <string name="track_recording_notification_accuracy">Precisión de ubicación: %1$s</string>
<string name="track_pace_notification">Ritmo: %1$s</string> <string name="track_pace_notification">Ritmo: %1$s</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="track_detail_intervals_tab">Intervalos</string> <string name="track_detail_intervals_tab">Intervalos</string>
<string name="interval_list_empty_message">Aún no hay ningún intervalo</string> <string name="interval_list_empty_message">Aún no hay ningún intervalo</string>
<string name="settings_recording_customize_layout_title">Personalice su pantalla de grabación</string> <string name="settings_recording_customize_layout_title">Personalice su pantalla de grabación</string>
-1
View File
@@ -458,7 +458,6 @@ limitations under the License.
<string name="settings_recording_show_on_lockscreen_while_recording_title">Показване на статистически данни на заключения екран</string> <string name="settings_recording_show_on_lockscreen_while_recording_title">Показване на статистически данни на заключения екран</string>
<string name="help_data_google_play_services_title">Изисква ли OpenTracks/използва ли услугата Google Play\?</string> <string name="help_data_google_play_services_title">Изисква ли OpenTracks/използва ли услугата Google Play\?</string>
<string name="about_version_code">Код на версията: %1$d</string> <string name="about_version_code">Код на версията: %1$d</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="export_error_post_workout">Експортът след тренировка е неуспешен, моля, проверете директорията за експорт.</string> <string name="export_error_post_workout">Експортът след тренировка е неуспешен, моля, проверете директорията за експорт.</string>
<string name="about_third_party_data">Данни от трети страни</string> <string name="about_third_party_data">Данни от трети страни</string>
<string name="import_unsupported_format">Неподдържан формат на файла</string> <string name="import_unsupported_format">Неподдържан формат на файла</string>
-1
View File
@@ -469,7 +469,6 @@ limitations under the License.
<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> <string name="about_third_party_data">Údaje třetích stran</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="delete_service">Služba mazání</string> <string name="delete_service">Služba mazání</string>
<string name="menu_voice_rate">Rychlost hlasových oznámení</string> <string name="menu_voice_rate">Rychlost hlasových oznámení</string>
<string name="track_delete_not_recording">Není možno vymazat aktuálně zaznamenávanou trasu.</string> <string name="track_delete_not_recording">Není možno vymazat aktuálně zaznamenávanou trasu.</string>
-1
View File
@@ -467,7 +467,6 @@ Hvis GPS-enheden rapporterer unøjagtige data (f.eks. Placering, hastighed, høj
<string name="track_speed_notification">Hastighed: %1$s</string> <string name="track_speed_notification">Hastighed: %1$s</string>
<string name="about_third_party_data">Data fra tredjeparter</string> <string name="about_third_party_data">Data fra tredjeparter</string>
<string name="delete_service">Tjeneste til sletning</string> <string name="delete_service">Tjeneste til sletning</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Stemmehastighed</string> <string name="menu_voice_rate">Stemmehastighed</string>
<string name="track_delete_not_recording">Dette spor kan ikke slettes, da det er optaget i øjeblikket.</string> <string name="track_delete_not_recording">Dette spor kan ikke slettes, da det er optaget i øjeblikket.</string>
<string name="settings_sensor_cycling_distance_speed">Afstand og hastighed</string> <string name="settings_sensor_cycling_distance_speed">Afstand og hastighed</string>
+3 -5
View File
@@ -276,9 +276,8 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
\n%1$s</string> \n%1$s</string>
<string name="share_track_subject">Ich möchte einen Track mit dir teilen</string> <string name="share_track_subject">Ich möchte einen Track mit dir teilen</string>
<!-- Stats --> <!-- Stats -->
<string name="stats_average_moving_pace">Durchschnitt\n(Bewegung)</string> <string name="stats_average_moving_pace">Durchschnittliches Bewegungstempo</string>
<string name="stats_average_moving_speed">Durchschnittsgeschwindigkeit <string name="stats_average_moving_speed">Mittlere Geschwindigkeit in Bewegung</string>
\n(Bewegung)</string>
<string name="stats_average_pace">Durchschnitt</string> <string name="stats_average_pace">Durchschnitt</string>
<string name="stats_average_speed">Durchschnittsgeschwindigkeit</string> <string name="stats_average_speed">Durchschnittsgeschwindigkeit</string>
<string name="stats_altitude">Höhe</string> <string name="stats_altitude">Höhe</string>
@@ -464,13 +463,12 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
<string name="about_third_party_data">Daten von Drittherstellern</string> <string name="about_third_party_data">Daten von Drittherstellern</string>
<string name="sensor_state_heart_rate_max">Maximaler Herzfrequenz</string> <string name="sensor_state_heart_rate_max">Maximaler Herzfrequenz</string>
<string name="track_delete_not_recording">Dieser Track kann nicht gelöscht werden, da er gerade aufgezeichnet wird.</string> <string name="track_delete_not_recording">Dieser Track kann nicht gelöscht werden, da er gerade aufgezeichnet wird.</string>
<string name="interval_list_empty_message">Es gibt noch keine Intervalle.</string> <string name="interval_list_empty_message">Es gibt noch keine Intervalle</string>
<string name="import_parser_error">Parser Fehler: %1$s</string> <string name="import_parser_error">Parser Fehler: %1$s</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Anzeige im Vollbildmodus während der Aufzeichnung.</string> <string name="settings_recording_fullscreen_on_while_recording_summary">Anzeige im Vollbildmodus während der Aufzeichnung.</string>
<string name="sensor_state_heart_rate_avg">Durchschnittlicher Herzfrequenz</string> <string name="sensor_state_heart_rate_avg">Durchschnittlicher Herzfrequenz</string>
<string name="settings_prevent_reimport_tracks_title">Verhindere den erneuten Import von Strecken(aufzeichnungen)</string> <string name="settings_prevent_reimport_tracks_title">Verhindere den erneuten Import von Strecken(aufzeichnungen)</string>
<string name="import_no_kml_file_found">Keine doc.kml Datei im KMZ gefunden</string> <string name="import_no_kml_file_found">Keine doc.kml Datei im KMZ gefunden</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="import_thread_interrupted">Thread unterbrochen</string> <string name="import_thread_interrupted">Thread unterbrochen</string>
<string name="import_unable_to_import_file">Importieren der Datei gescheitert: %1$s</string> <string name="import_unable_to_import_file">Importieren der Datei gescheitert: %1$s</string>
<string name="delete_service">Löschdienst</string> <string name="delete_service">Löschdienst</string>
-1
View File
@@ -464,7 +464,6 @@ limitations under the License.
<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> <string name="about_third_party_data">Datos de terceros</string>
<string name="delete_service">Borrar servicio</string> <string name="delete_service">Borrar servicio</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Velocidad de la voz</string> <string name="menu_voice_rate">Velocidad de la voz</string>
<string name="track_delete_not_recording">Este recorrido no puede ser eliminado porque se está grabando actualmente.</string> <string name="track_delete_not_recording">Este recorrido no puede ser eliminado porque se está grabando actualmente.</string>
<string name="settings_sensor_running_speed_and_cadence">Distancia, velocidad y cadencia</string> <string name="settings_sensor_running_speed_and_cadence">Distancia, velocidad y cadencia</string>
-1
View File
@@ -464,7 +464,6 @@ limitations under the License.
<string name="track_pace_notification">Tempo: %1$s</string> <string name="track_pace_notification">Tempo: %1$s</string>
<string name="about_third_party_data">Kolmanda osapoole andmed</string> <string name="about_third_party_data">Kolmanda osapoole andmed</string>
<string name="delete_service">Kustutamise teenus</string> <string name="delete_service">Kustutamise teenus</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Hääle kiirus</string> <string name="menu_voice_rate">Hääle kiirus</string>
<string name="track_delete_not_recording">Seda pala ei saa kustutada, kuna see on praegu salvestatud.</string> <string name="track_delete_not_recording">Seda pala ei saa kustutada, kuna see on praegu salvestatud.</string>
<string name="settings_sensor_cycling_cadence">Rütm</string> <string name="settings_sensor_cycling_cadence">Rütm</string>
-1
View File
@@ -467,7 +467,6 @@ GPS gailuak datu okerrak salatzen baditu (adibidez, kokapena, abiadura, kota), O
<string name="track_pace_notification">Erritmoa: %1$s</string> <string name="track_pace_notification">Erritmoa: %1$s</string>
<string name="about_third_party_data">Hirugarrenen datuak</string> <string name="about_third_party_data">Hirugarrenen datuak</string>
<string name="delete_service">Ezabatzeko zerbitzua</string> <string name="delete_service">Ezabatzeko zerbitzua</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Ahots abiadura</string> <string name="menu_voice_rate">Ahots abiadura</string>
<string name="track_delete_not_recording">Pista hau ezin da ezabatu da gaur egun grabatzen.</string> <string name="track_delete_not_recording">Pista hau ezin da ezabatu da gaur egun grabatzen.</string>
<string name="settings_sensor_cycling_cadence">Kadentzia</string> <string name="settings_sensor_cycling_cadence">Kadentzia</string>
-1
View File
@@ -469,7 +469,6 @@ limitations under the License.
<string name="track_speed_notification">Nopeus: %1$s</string> <string name="track_speed_notification">Nopeus: %1$s</string>
<string name="about_third_party_data">Kolmannen osapuolen tiedot</string> <string name="about_third_party_data">Kolmannen osapuolen tiedot</string>
<string name="delete_service">Poistopalvelu</string> <string name="delete_service">Poistopalvelu</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Äänen nopeus</string> <string name="menu_voice_rate">Äänen nopeus</string>
<string name="track_delete_not_recording">Tätä reittiä ei voi poistaa koska sitä tallennetaan parhaillaan.</string> <string name="track_delete_not_recording">Tätä reittiä ei voi poistaa koska sitä tallennetaan parhaillaan.</string>
<string name="settings_sensor_cycling_cadence">Pyöräily: tahtiaika</string> <string name="settings_sensor_cycling_cadence">Pyöräily: tahtiaika</string>
-1
View File
@@ -465,7 +465,6 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
<string name="export_track_already_exists_msg">La trace %1$s existe déjà dans le répertoire de destination.</string> <string name="export_track_already_exists_msg">La trace %1$s existe déjà dans le répertoire de destination.</string>
<string name="export_progress_summary_overwrite_msg">Traces écrasées</string> <string name="export_progress_summary_overwrite_msg">Traces écrasées</string>
<string name="about_third_party_data">Données de tiers</string> <string name="about_third_party_data">Données de tiers</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="delete_service">Service de suppression</string> <string name="delete_service">Service de suppression</string>
<string name="menu_voice_rate">Vitesse vocale</string> <string name="menu_voice_rate">Vitesse vocale</string>
<string name="track_delete_not_recording">Cette piste ne peut pas être supprimée car elle est en cours d\'enregistrement.</string> <string name="track_delete_not_recording">Cette piste ne peut pas être supprimée car elle est en cours d\'enregistrement.</string>
-1
View File
@@ -472,7 +472,6 @@ Se te detés ou estás en interiores, non se gardarán datos do sensor (mais ser
<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> <string name="about_third_party_data">Datos de terceiros alleos</string>
<string name="delete_service">Eliminación de servizo</string> <string name="delete_service">Eliminación de servizo</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Velocidade da voz</string> <string name="menu_voice_rate">Velocidade da voz</string>
<string name="track_delete_not_recording">Esta pista non se pode eliminar porque está a funcionar a gravación.</string> <string name="track_delete_not_recording">Esta pista non se pode eliminar porque está a funcionar a gravación.</string>
<string name="settings_sensor_cycling_distance_speed">Distancia e Velocidade</string> <string name="settings_sensor_cycling_distance_speed">Distancia e Velocidade</string>
-1
View File
@@ -460,7 +460,6 @@ limitations under the License.
\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> <string name="about_third_party_data">Harmadik féltől származó adatok</string>
<string name="delete_service">Törlési szolgáltatás</string> <string name="delete_service">Törlési szolgáltatás</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Hang sebesség</string> <string name="menu_voice_rate">Hang sebesség</string>
<string name="track_delete_not_recording">Ez a sáv nem törölhető, mivel jelenleg is rögzítve van.</string> <string name="track_delete_not_recording">Ez a sáv nem törölhető, mivel jelenleg is rögzítve van.</string>
<string name="settings_sensor_cycling_cadence">Kerékpározás: lépésszám</string> <string name="settings_sensor_cycling_cadence">Kerékpározás: lépésszám</string>
-1
View File
@@ -471,7 +471,6 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
<string name="track_pace_notification">Ritmo: %1$s</string> <string name="track_pace_notification">Ritmo: %1$s</string>
<string name="about_third_party_data">Dati di terzi</string> <string name="about_third_party_data">Dati di terzi</string>
<string name="delete_service">Servizio di eliminazione</string> <string name="delete_service">Servizio di eliminazione</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Velocità della voce</string> <string name="menu_voice_rate">Velocità della voce</string>
<string name="track_delete_not_recording">Questa traccia non può essere cancellata perché è attualmente in registrazione.</string> <string name="track_delete_not_recording">Questa traccia non può essere cancellata perché è attualmente in registrazione.</string>
<string name="settings_sensor_cycling_cadence">Cadenza</string> <string name="settings_sensor_cycling_cadence">Cadenza</string>
-1
View File
@@ -416,7 +416,6 @@ limitations under the License.
<string name="help_recording_content">OpenTracks naudoja prietaiso GPS jutiklį, kad nuolat įrašytų vietą. Jei GPS įrenginys praneša apie netikslius duomenis (pvz., Vietą, greitį, aukštį), OpenTracks negali nieko padaryti.</string> <string name="help_recording_content">OpenTracks naudoja prietaiso GPS jutiklį, kad nuolat įrašytų vietą. Jei GPS įrenginys praneša apie netikslius duomenis (pvz., Vietą, greitį, aukštį), OpenTracks negali nieko padaryti.</string>
<string name="generic_open_track">Atidaryti takelį</string> <string name="generic_open_track">Atidaryti takelį</string>
<string name="settings_recording_keep_screen_on_while_recording_summary">Įrašydami palaikykite ekraną įjungtą.</string> <string name="settings_recording_keep_screen_on_while_recording_summary">Įrašydami palaikykite ekraną įjungtą.</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="open_track_as_GPX">Atidaryti kaip GPX</string> <string name="open_track_as_GPX">Atidaryti kaip GPX</string>
<string name="help_gps_troubleshooting_title">OpenTracks nefiksuoja vietų\?</string> <string name="help_gps_troubleshooting_title">OpenTracks nefiksuoja vietų\?</string>
<string name="generic_overwrite">Perrašyti</string> <string name="generic_overwrite">Perrašyti</string>
-1
View File
@@ -416,7 +416,6 @@ limitations under the License.
<string name="help_track_export_title">Kā es varu eksportēt/ kopīgot ierakstītos ierakstus\?</string> <string name="help_track_export_title">Kā es varu eksportēt/ kopīgot ierakstītos ierakstus\?</string>
<string name="instant_export_enabled_summary">Dziesmas eksportēšana uz krātuvi pēc ierakstīšanas pabeigšanas</string> <string name="instant_export_enabled_summary">Dziesmas eksportēšana uz krātuvi pēc ierakstīšanas pabeigšanas</string>
<string name="activity_type_escooter">eScooter</string> <string name="activity_type_escooter">eScooter</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="sensor_state_power_avg">Vidējā jauda</string> <string name="sensor_state_power_avg">Vidējā jauda</string>
<string name="about_libraries">Bibliotēkas</string> <string name="about_libraries">Bibliotēkas</string>
<string name="description_default_speed_or_pace">Pēc aktivitātes veida</string> <string name="description_default_speed_or_pace">Pēc aktivitātes veida</string>
-1
View File
@@ -460,7 +460,6 @@ limitations under the License.
<string name="track_pace_notification">Tempo: %1$s</string> <string name="track_pace_notification">Tempo: %1$s</string>
<string name="about_third_party_data">Tredjepartsdata</string> <string name="about_third_party_data">Tredjepartsdata</string>
<string name="delete_service">Slettingstjeneste</string> <string name="delete_service">Slettingstjeneste</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Talehastighet</string> <string name="menu_voice_rate">Talehastighet</string>
<string name="track_delete_not_recording">Dette sporet kan ikke slettes da det tas opp nå.</string> <string name="track_delete_not_recording">Dette sporet kan ikke slettes da det tas opp nå.</string>
<string name="settings_sensor_cycling_cadence">Kadens</string> <string name="settings_sensor_cycling_cadence">Kadens</string>
-1
View File
@@ -465,7 +465,6 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
<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> <string name="about_third_party_data">Gegevens van derden</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="delete_service">Dienst verwijdering</string> <string name="delete_service">Dienst verwijdering</string>
<string name="menu_voice_rate">Spraak snelheid</string> <string name="menu_voice_rate">Spraak snelheid</string>
<string name="track_delete_not_recording">Dit fragment kan niet worden gewist omdat het momenteel is opgenomen.</string> <string name="track_delete_not_recording">Dit fragment kan niet worden gewist omdat het momenteel is opgenomen.</string>
-1
View File
@@ -476,7 +476,6 @@ limitations under the License.
<string name="track_distance_notification">Odległość: %1$s</string> <string name="track_distance_notification">Odległość: %1$s</string>
<string name="about_third_party_data">Dane osób trzecich</string> <string name="about_third_party_data">Dane osób trzecich</string>
<string name="delete_service">Usługa usuwania</string> <string name="delete_service">Usługa usuwania</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Szybkość głosu</string> <string name="menu_voice_rate">Szybkość głosu</string>
<string name="track_delete_not_recording">Tej ścieżki nie można usunąć, ponieważ jest ona aktualnie nagrywana.</string> <string name="track_delete_not_recording">Tej ścieżki nie można usunąć, ponieważ jest ona aktualnie nagrywana.</string>
<string name="settings_sensor_cycling_distance_speed">Dystans i Prędkość</string> <string name="settings_sensor_cycling_distance_speed">Dystans i Prędkość</string>
-1
View File
@@ -404,7 +404,6 @@
<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> <string name="about_third_party_data">Dados de terceiros</string>
<string name="delete_service">Serviço de exclusão</string> <string name="delete_service">Serviço de exclusão</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Velocidade da voz</string> <string name="menu_voice_rate">Velocidade da voz</string>
<string name="track_delete_not_recording">Esta trilha não pode ser excluída porque está gravada no momento.</string> <string name="track_delete_not_recording">Esta trilha não pode ser excluída porque está gravada no momento.</string>
<plurals name="voiceSpeedKilometersPerHour"> <plurals name="voiceSpeedKilometersPerHour">
-1
View File
@@ -426,7 +426,6 @@ limitations under the License.
<string name="import_progress_summary_exists_msg">Există deja</string> <string name="import_progress_summary_exists_msg">Există deja</string>
<string name="track_detail_intervals_tab">Intervaluri</string> <string name="track_detail_intervals_tab">Intervaluri</string>
<string name="track_recording_notification_accuracy">Precizia locației: %1$s</string> <string name="track_recording_notification_accuracy">Precizia locației: %1$s</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="sensor_state_cadence_max">Max Cadență</string> <string name="sensor_state_cadence_max">Max Cadență</string>
<string name="sensor_could_not_scan">Nu a fost posibilă scanarea pentru dispozitive Bluetooth (eroare %1$i).</string> <string name="sensor_could_not_scan">Nu a fost posibilă scanarea pentru dispozitive Bluetooth (eroare %1$i).</string>
<string name="share_image_subject">Aș dori să vă împărtășesc o imagine</string> <string name="share_image_subject">Aș dori să vă împărtășesc o imagine</string>
+66 -38
View File
@@ -24,7 +24,7 @@ limitations under the License.
<string name="activity_type_commercial_airplane">Пассажирские самолеты</string> <string name="activity_type_commercial_airplane">Пассажирские самолеты</string>
<string name="activity_type_cross_country_skiing">лыжные гонки</string> <string name="activity_type_cross_country_skiing">лыжные гонки</string>
<string name="activity_type_cycling">Езда на велосипеде</string> <string name="activity_type_cycling">Езда на велосипеде</string>
<string name="activity_type_dirt_bike">Езда на велосипеде по бездорожью</string> <string name="activity_type_dirt_bike">Дертбайк</string>
<string name="activity_type_donkey_back_riding">Верховая езда на ослах</string> <string name="activity_type_donkey_back_riding">Верховая езда на ослах</string>
<string name="activity_type_driving">на автомобиле</string> <string name="activity_type_driving">на автомобиле</string>
<string name="activity_type_driving_bus">Вождение автобуса</string> <string name="activity_type_driving_bus">Вождение автобуса</string>
@@ -54,7 +54,7 @@ limitations under the License.
<string name="activity_type_rc_boat">Радиоуправляемые лодки</string> <string name="activity_type_rc_boat">Радиоуправляемые лодки</string>
<string name="activity_type_rc_helicopter">Радиоуправляемые вертолеты</string> <string name="activity_type_rc_helicopter">Радиоуправляемые вертолеты</string>
<string name="activity_type_riding">Верховая езда</string> <string name="activity_type_riding">Верховая езда</string>
<string name="activity_type_road_biking">Езда на шоссейном велосипеде</string> <string name="activity_type_road_biking">Шоссейный велосипед</string>
<string name="activity_type_roller_skiing">роликовые лыжи</string> <string name="activity_type_roller_skiing">роликовые лыжи</string>
<string name="activity_type_rowing">Гребля академическая</string> <string name="activity_type_rowing">Гребля академическая</string>
<string name="activity_type_running">Бег</string> <string name="activity_type_running">Бег</string>
@@ -128,7 +128,7 @@ limitations under the License.
<string name="image_arrow">Стрелка</string> <string name="image_arrow">Стрелка</string>
<string name="image_marker">Маркер</string> <string name="image_marker">Маркер</string>
<string name="image_marker_count">Количество маркеров</string> <string name="image_marker_count">Количество маркеров</string>
<string name="image_photo_marker">Маркер фотографии</string> <string name="image_photo_marker">Фотомаркер</string>
<string name="image_record">Запись</string> <string name="image_record">Запись</string>
<string name="image_stop">Стоп</string> <string name="image_stop">Стоп</string>
<string name="image_track">Трек</string> <string name="image_track">Трек</string>
@@ -212,7 +212,7 @@ limitations under the License.
<string name="stats_average_speed">Средняя скорость</string> <string name="stats_average_speed">Средняя скорость</string>
<string name="stats_altitude">Высота</string> <string name="stats_altitude">Высота</string>
<string name="stats_fastest_pace">Макс. темп</string> <string name="stats_fastest_pace">Макс. темп</string>
<string name="stats_gain">Рост</string> <string name="stats_gain">Набор высоты</string>
<string name="stats_latitude">Широта</string> <string name="stats_latitude">Широта</string>
<string name="stats_longitude">Долгота</string> <string name="stats_longitude">Долгота</string>
<string name="stats_max_speed">Макс. скорость</string> <string name="stats_max_speed">Макс. скорость</string>
@@ -337,53 +337,53 @@ limitations under the License.
<string name="waypoint_type_telephone">Телефон</string> <string name="waypoint_type_telephone">Телефон</string>
<string name="waypoint_type_theater">Театр</string> <string name="waypoint_type_theater">Театр</string>
<string name="waypoint_type_toilet">Туалет</string> <string name="waypoint_type_toilet">Туалет</string>
<string name="waypoint_type_tourist_information">Туристическая информация</string> <string name="waypoint_type_tourist_information">Информация для туристов</string>
<string name="waypoint_type_tree">Дерево</string> <string name="waypoint_type_tree">Дерево</string>
<string name="waypoint_type_tunnel">Тоннель</string> <string name="waypoint_type_tunnel">Тоннель</string>
<string name="waypoint_type_waterfall">Водопад</string> <string name="waypoint_type_waterfall">Водопад</string>
<string name="waypoint_type_water_fountain">Фонтан</string> <string name="waypoint_type_water_fountain">Фонтан</string>
<string name="waypoint_type_waypoint">путевая точка</string> <string name="waypoint_type_waypoint">путевая точка</string>
<string name="help_track_map_content"> Используйте значок маленькой карты. <string name="help_track_map_content"> Используйте маленький значок карты.
\na) (живые обновления) Установите <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Он предоставляет карты в режиме онлайн или оффлайн с помощью <a href="https://www.openstreetmap.org">OpenStreetMap</a>. \na) (живые обновления) Установите <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Он предоставляет карты в режиме онлайн или оффлайн с помощью <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
\nНа самом деле, OSMDashboard - это отдельное приложение, которое может получить доступ (временно) только к данным треков, которые будут показаны. \nНа самом деле, OSMDashboard — это отдельное приложение, которое может получить доступ (временно) только к данным треков, которые будут показаны.
\nb) Через обычное картографическое приложение (например, <a href="http://osmand.net/">OsmAnd</a> и <a href="https://maps.me/">MAPS.ME</a>). Эти приложения должны поддерживать формат файлов KMZ (только местоположения и метки времени). Фактически, приложение должно поддерживать <i>ACTION_VIEW</i> с MIME <i>application/vnd.google-earth.kmz</i>. </string> \nb) Через обычное картографическое приложение (например, <a href="http://osmand.net/">OsmAnd</a> и <a href="https://maps.me/">MAPS.ME</a>). Эти приложения должны поддерживать формат файлов KMZ (только местоположения и метки времени). Фактически, приложение должно поддерживать <i>ACTION_VIEW</i> с MIME <i>application/vnd.google-earth.kmz</i>. </string>
<string name="help_storage_pictures_title">Как OpenTracks хранит изображения, сделанные в качестве маркеров\?</string> <string name="help_storage_pictures_title">Как OpenTracks хранит изображения, сделанные в качестве маркеров\?</string>
<string name="about_preference_title">О OpenTracks</string> <string name="about_preference_title">Об OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> - это приложение для отслеживания спортивных событий, которое полностью уважает вашу конфиденциальность.</string> <string name="about_description"><i>OpenTracks</i> — это приложение для отслеживания спортивных событий, которое полностью уважает вашу конфиденциальность.</string>
<string name="about_url">Веб-страница проекта: <a href="%1$s">%1$s</a></string> <string name="about_url">Веб-страница проекта: <a href="%1$s">%1$s</a></string>
<string name="about_version_name">Название версии: %1$s</string> <string name="about_version_name">Название версии: %1$s</string>
<string name="about_version_code">Код версии: %1$d</string> <string name="about_version_code">Код версии: %1$d</string>
<string name="about_history">OpenTracks - это форк <a href="https://code.google.com/archive/p/mytracks/"><i>Google MyTracks</i></a> (разработка прекращена в 2016 г.).</string> <string name="about_history">OpenTracks — это форк <a href="https://code.google.com/archive/p/mytracks/"><i>Google MyTracks</i></a> (разработка прекращена в 2016 г.).</string>
<string name="about_libraries">Библиотеки</string> <string name="about_libraries">Библиотеки</string>
<string name="help_feature_title">OpenTracks имеет следующие возможности:</string> <string name="help_feature_title">OpenTracks имеет следующие возможности:</string>
<string name="help_feature_content">* создание GPS-треков <string name="help_feature_content">* создание GPS-треков
\n* использование датчика частоты пульса (только Bluetooth LE) \n* использование датчика частоты пульса (только Bluetooth LE)
\n* голосовые объявления (по времени или расстоянию) \n* голосовые повещения (по времени или расстоянию)
\n* создание маркеров (в т.ч. картинки) \n* создание маркеров (в т.ч. с изображениями)
\n* работа полностью офлайн \n* работа полностью офлайн
\n* показ треков на карте (см. ниже)</string> \n* показ треков на карте (см. ниже)</string>
<string name="help_track_map_title">Как показать трек на карте\?</string> <string name="help_track_map_title">Как показать трек на карте\?</string>
<string name="help_storage_pictures_content">Они хранятся во внутренней памяти приложения (т. е. доступны только в OpenTracks).</string> <string name="help_storage_pictures_content">Они хранятся во внутренней памяти приложения (т. е. доступны только в OpenTracks).</string>
<string name="help_track_export_title">Как я могу экспортировать/поделиться записанными треками\?</string> <string name="help_track_export_title">Как я могу экспортировать/поделиться записанными треками\?</string>
<string name="help_data_google_play_services_title">Требуется/используется ли OpenTracks сервисы Google Play\?</string> <string name="help_data_google_play_services_title">Требует/использует ли OpenTracks сервисы Google Play\?</string>
<string name="help_recording_title">Как OpenTracks записывает данные\?</string> <string name="help_recording_title">Как OpenTracks записывает данные\?</string>
<string name="help_track_export_content"> Трек может быть экспортирован из \"Настроек\" в виде KMZ, KML или GPX. KMZ/KML все данные; GPX только местоположения. Поделиться треком можно через значок <i>поделиться</i> предоставляется как KMZ. </string> <string name="help_track_export_content"> Трек может быть экспортирован из \"Настроек\" в виде KMZ, KML или GPX. KMZ/KML все данные; GPX только местоположения. Поделиться треком можно через значок <i>поделиться</i> в формате KMZ. </string>
<string name="help_recording_content">OpenTracks использует датчик GPS устройства для непрерывной записи местоположения. Если устройство GPS сообщает неточные данные (например, местоположение, скорость, высоту), OpenTracks ничего не может с этим поделать.</string> <string name="help_recording_content">OpenTracks использует датчик GPS устройства для непрерывной записи местоположения. Если устройство GPS сообщает неточные данные (например, местоположение, скорость, высоту), OpenTracks ничего не может с этим поделать.</string>
<string name="export_progress_summary_new_msg">Новые экспортируемые треки</string> <string name="export_progress_summary_new_msg">Новых треков экспортировано</string>
<string name="help_data_storage_title">Как OpenTracks записывает данные с датчиков\?</string> <string name="help_data_storage_title">Как OpenTracks записывает данные с датчиков\?</string>
<string name="export_track_already_exists_msg">Дорожка %1$s уже существует в каталоге назначения.</string> <string name="export_track_already_exists_msg">Дорожка %1$s уже существует в каталоге назначения.</string>
<string name="export_without_photos">без фотографий</string> <string name="export_without_photos">без фотографий</string>
<string name="export_with_photos">с фотографией</string> <string name="export_with_photos">с фотографией</string>
<string name="description_default_speed_or_pace">По типу деятельности</string> <string name="description_default_speed_or_pace">По типу деятельности</string>
<string name="export_all_do_it_for_all">делать это во всех конфликтах</string> <string name="export_all_do_it_for_all">для всех конфликтов</string>
<string name="stats_loss">Потери</string> <string name="stats_loss">Спуск</string>
<string name="export_progress_summary_skip_msg">Пропущенные дорожки</string> <string name="export_progress_summary_skip_msg">Пропущенные треки</string>
<string name="generic_click_twice_cancel">Нажмите назад, чтобы отменить</string> <string name="generic_click_twice_cancel">Нажмите назад еще раз, чтобы отменить</string>
<string name="generic_completed">Завершено</string> <string name="generic_completed">Завершено</string>
<string name="generic_show_errors">Показать ошибки</string> <string name="generic_show_errors">Показать ошибки</string>
<string name="generic_separator_done_total">/</string> <string name="generic_separator_done_total">/</string>
<string name="generic_skip">Пропустить</string> <string name="generic_skip">Пропустить</string>
<string name="generic_open_track">Открытая трасса</string> <string name="generic_open_track">Открыть трек</string>
<string name="export_progress_summary_errors_msg">Ошибки</string> <string name="export_progress_summary_errors_msg">Ошибки</string>
<string name="import_file_imported">Файл %1$s импортирован</string> <string name="import_file_imported">Файл %1$s импортирован</string>
<string name="import_error_list_dialog_title">Файлы не импортированы</string> <string name="import_error_list_dialog_title">Файлы не импортированы</string>
@@ -405,7 +405,7 @@ limitations under the License.
<string name="open_track_as_KML">Открыть как KML</string> <string name="open_track_as_KML">Открыть как KML</string>
<string name="help_support_title">Как поддержать OpenTracks\?</string> <string name="help_support_title">Как поддержать OpenTracks\?</string>
<string name="help_gps_troubleshooting_title">OpenTracks не регистрирует местоположения\?</string> <string name="help_gps_troubleshooting_title">OpenTracks не регистрирует местоположения\?</string>
<string name="activity_type_escooter">эскулап</string> <string name="activity_type_escooter">Электросамокат</string>
<string name="permission_gps_failed">OpenTracks требует разрешения на использование GPS.</string> <string name="permission_gps_failed">OpenTracks требует разрешения на использование GPS.</string>
<string name="settings_prevent_reimport_tracks_title">Предотвращение повторного импорта треков</string> <string name="settings_prevent_reimport_tracks_title">Предотвращение повторного импорта треков</string>
<string name="sensor_state_cadence_max">Макс. каденция</string> <string name="sensor_state_cadence_max">Макс. каденция</string>
@@ -420,33 +420,33 @@ limitations under the License.
<string name="bluetooth_disabled">Включите Bluetooth.</string> <string name="bluetooth_disabled">Включите Bluetooth.</string>
<string name="sensor_state_power_avg">Средняя мощность</string> <string name="sensor_state_power_avg">Средняя мощность</string>
<string name="track_recording_notification_accuracy">Точность расположения: %1$s</string> <string name="track_recording_notification_accuracy">Точность расположения: %1$s</string>
<string name="stats_split_interval">Раздельный интервал</string> <string name="stats_split_interval">Размер интервалов</string>
<string name="settings_sensor_wheel_circumference">Окружность колеса (мм)</string> <string name="settings_sensor_wheel_circumference">Окружность колеса (мм)</string>
<string name="settings_night_mode_option_night">Ночная</string> <string name="settings_night_mode_option_night">Ночная</string>
<string name="settings_night_mode_option_day">Дневная</string> <string name="settings_night_mode_option_day">Дневная</string>
<string name="export_progress_summary_overwrite_msg">Перезапись дорожек</string> <string name="export_progress_summary_overwrite_msg">Перезапись треков</string>
<string name="export_track_errors">Треки не экспортируются</string> <string name="export_track_errors">Треки не экспортированы</string>
<string name="export_error_post_workout">Сбой экспорта после тренировки, пожалуйста, проверьте каталог экспорта.</string> <string name="export_error_post_workout">Сбой экспорта после тренировки, пожалуйста, проверьте каталог экспорта.</string>
<string name="description_altitude_loss">Потеря высоты: %1$d м (%2$d футов)</string> <string name="description_altitude_loss">Потеря высоты: %1$d м (%2$d футов)</string>
<string name="settings_default_export_uri_title">Каталог экспорта треков</string> <string name="settings_default_export_uri_title">Каталог экспорта треков</string>
<string name="instant_export_enabled_summary">Экспорт дорожки в хранилище после завершения записи</string> <string name="instant_export_enabled_summary">Экспортировать трек в хранилище после завершения записи</string>
<string name="share_image_subject">Я хотел бы поделиться с вами изображением</string> <string name="share_image_subject">Я хотел бы поделиться с вами изображением</string>
<string name="track_detail_intervals_tab">Интервалы</string> <string name="track_detail_intervals_tab">Интервалы</string>
<string name="help_data_storage_content"> Данные датчика хранятся для каждой записи. Для датчика пульса это означает, что для каждой записи сохраняется только <i>одно</i> измерение пульса. Если вы стоите на месте или находитесь в помещении, данные датчика не будут записаны (но будут отображаться). </string> <string name="help_data_storage_content"> Данные датчиков сохраняются для каждого местоположения. Для датчика пульса это означает, что для местоположения сохраняется только <i>одно</i> значение пульса. Если вы стоите на месте или находитесь в помещении, данные датчика не будут записываться (но будут отображаться). </string>
<string name="help_gps_troubleshooting_content"> Что вы можете сделать: <string name="help_gps_troubleshooting_content"> Что можно сделать:
\n a) проверить, включен ли GPS, \n a) проверить, включен ли GPS,
\n б) улучшить прием GPS (например, выехать на природу), \n б) улучшить прием GPS (например, выйти из помещения),
\n c) проверить с помощью приложения <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a>, и \n c) проверить с помощью приложения <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a>, и
\n г) отключите энергосбережение. \n г) отключть энергосбережение.
\n \n
\n Если все это не поможет, пожалуйста, сообщите об ошибке. </string> \n Если все это не поможет, пожалуйста, сообщите об ошибке. </string>
<string name="track_pace_notification">Темп: %1$s</string> <string name="track_pace_notification">Темп: %1$s</string>
<string name="track_speed_notification">Скорость: %1$s</string> <string name="track_speed_notification">Скорость: %1$s</string>
<string name="track_distance_notification">Расстояние: %1$s</string> <string name="track_distance_notification">Расстояние: %1$s</string>
<string name="interval_list_empty_message">Интервалов пока нет</string> <string name="interval_list_empty_message">Интервалов пока нет</string>
<string name="aggregated_stats_empty_message">Запишите свой первый трек, чтобы увидеть агрегированную статистику</string> <string name="aggregated_stats_empty_message">Запишите свой первый трек, чтобы увидеть собранную статистику</string>
<string name="instant_export_enabled_title">Мгновенный экспорт после тренировки</string> <string name="instant_export_enabled_title">Мгновенный экспорт после тренировки</string>
<string name="hold_to_stop">Удерживать для остановки</string> <string name="hold_to_stop">Удерживайте для остановки</string>
<string name="share_image_body">Я думаю, вас может заинтересовать это изображение.</string> <string name="share_image_body">Я думаю, вас может заинтересовать это изображение.</string>
<string name="help_support_content"> Вы можете: <string name="help_support_content"> Вы можете:
\n a) распространять информацию об OpenTracks, \n a) распространять информацию об OpenTracks,
@@ -463,12 +463,11 @@ limitations under the License.
<string name="sensor_could_not_scan">Не удалось выполнить сканирование устройств Bluetooth (ошибка %1$i).</string> <string name="sensor_could_not_scan">Не удалось выполнить сканирование устройств Bluetooth (ошибка %1$i).</string>
<string name="import_progress_summary_errors_msg">Ошибки</string> <string name="import_progress_summary_errors_msg">Ошибки</string>
<string name="generic_overwrite">Перезаписать</string> <string name="generic_overwrite">Перезаписать</string>
<string name="import_prevent_reimport">Вы настроили предотвращение повторного импорта трека</string> <string name="import_prevent_reimport">Вы запретили повторный импорт трека</string>
<string name="delete_service">Услуга удаления</string> <string name="delete_service">Услуга удаления</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="about_third_party_data">Сторонние данные</string> <string name="about_third_party_data">Сторонние данные</string>
<string name="menu_voice_rate">Скорость голоса</string> <string name="menu_voice_rate">Скорость голоса</string>
<string name="track_delete_not_recording">Эта дорожка не может быть удалена, поскольку она записана в данный момент.</string> <string name="track_delete_not_recording">Этот трек нельзя удалить, так как он записывается в данный момент.</string>
<plurals name="generic_completed_with_errors"> <plurals name="generic_completed_with_errors">
<item quantity="one">Завершена с %1$d ошибкой</item> <item quantity="one">Завершена с %1$d ошибкой</item>
<item quantity="few">Завершено с %1$d ошибками</item> <item quantity="few">Завершено с %1$d ошибками</item>
@@ -478,7 +477,7 @@ limitations under the License.
<string name="settings_sensor_cycling_cadence">Каденция</string> <string name="settings_sensor_cycling_cadence">Каденция</string>
<string name="settings_sensor_cycling_distance_speed">Дистанция и Скорость</string> <string name="settings_sensor_cycling_distance_speed">Дистанция и Скорость</string>
<string name="settings_sensor_running_speed_and_cadence">Дистанция, Скорость и Каденция</string> <string name="settings_sensor_running_speed_and_cadence">Дистанция, Скорость и Каденция</string>
<string name="stats_sensors_cadence">Каденция</string> <string name="stats_sensors_cadence">Каденс</string>
<string name="activity_type_swimming">Плавание</string> <string name="activity_type_swimming">Плавание</string>
<string name="activity_type_swimming_open">Плавание на открытой воде</string> <string name="activity_type_swimming_open">Плавание на открытой воде</string>
<string name="activity_type_workout">Тренировка</string> <string name="activity_type_workout">Тренировка</string>
@@ -523,7 +522,7 @@ limitations under the License.
</plurals> </plurals>
<string name="settings_layout_reset">Сбросить свои раскладки</string> <string name="settings_layout_reset">Сбросить свои раскладки</string>
<string name="settings_layout_reset_done">Настройки всех раскладок были сброшены</string> <string name="settings_layout_reset_done">Настройки всех раскладок были сброшены</string>
<string name="generic_to">До</string> <string name="generic_to">по</string>
<string name="generic_today">Сегодня</string> <string name="generic_today">Сегодня</string>
<string name="generic_yesterday">Вчера</string> <string name="generic_yesterday">Вчера</string>
<string name="settings_running_sensor">Бег</string> <string name="settings_running_sensor">Бег</string>
@@ -537,7 +536,7 @@ limitations under the License.
<string name="settings_layout_reset_confirm_title">Сбросить свои раскладки\?</string> <string name="settings_layout_reset_confirm_title">Сбросить свои раскладки\?</string>
<string name="settings_layout_reset_confirm_message">Настройки всех раскладок будут сброшены на значения по умолчанию. Это не удалит треки на устройстве.</string> <string name="settings_layout_reset_confirm_message">Настройки всех раскладок будут сброшены на значения по умолчанию. Это не удалит треки на устройстве.</string>
<string name="stats_sensors_heart_rate">Пульс</string> <string name="stats_sensors_heart_rate">Пульс</string>
<string name="stats_sensors_power">Энергия</string> <string name="stats_sensors_power">Мощность</string>
<string name="value_float_mile_hour">%1$.1f миль/ч</string> <string name="value_float_mile_hour">%1$.1f миль/ч</string>
<string name="voice_per_kilometer">за километр</string> <string name="voice_per_kilometer">за километр</string>
<string name="voice_per_mile">за милю</string> <string name="voice_per_mile">за милю</string>
@@ -555,7 +554,7 @@ limitations under the License.
<string name="average_heart_rate">Средний пульс</string> <string name="average_heart_rate">Средний пульс</string>
<string name="current_heart_rate">Текущий пульс</string> <string name="current_heart_rate">Текущий пульс</string>
<string name="generic_filter">Фильтр</string> <string name="generic_filter">Фильтр</string>
<string name="generic_from">От</string> <string name="generic_from">с</string>
<string name="settings_export_section">Экспорт</string> <string name="settings_export_section">Экспорт</string>
<string name="settings_import_section">Импорт</string> <string name="settings_import_section">Импорт</string>
<string name="settings_api_title">Публичный API</string> <string name="settings_api_title">Публичный API</string>
@@ -587,4 +586,33 @@ limitations under the License.
<string name="error_crash_title">%1$s вылетел</string> <string name="error_crash_title">%1$s вылетел</string>
<string name="share_crash_report">Поделиться отчётом о вылете</string> <string name="share_crash_report">Поделиться отчётом о вылете</string>
<string name="report_bug">Сообщить об ошибке</string> <string name="report_bug">Сообщить об ошибке</string>
<plurals name="voiceSpeedMKnots">
<item quantity="one">%1$.1f узел</item>
<item quantity="few">%1$.1f узла</item>
<item quantity="many">%1$.1f узлов</item>
<item quantity="other">%1$.1f узлов</item>
</plurals>
<string name="unit_knots">уз</string>
<plurals name="voiceDistanceNauticalMiles">
<item quantity="one">%1$.2f морская миля</item>
<item quantity="few">%1$.2f морские мили</item>
<item quantity="many">%1$.2f морских миль</item>
<item quantity="other">%1$.2f морских миль</item>
</plurals>
<string name="value_integer_knots">%1$d узлов</string>
<string name="value_float_knots">%1$.1f узла</string>
<string name="image_discard">Отменить</string>
<string name="track_discarding">Отмена записи трека</string>
<string name="settings_announcements_lap_heart_rate">Значение пульса за круг</string>
<string name="settings_announcements_lap_speed_pace">Скорость/темп за круг</string>
<string name="settings_public_api_package_title">Имя пакета для публичного API</string>
<string name="description_speed_nautical">Скорость (узлы)</string>
<string name="value_float_knots_recommended">%1$.1f узла (рекомендуется)</string>
<string name="settings_announcements_average_heart_rate">Средний пульс</string>
<string name="settings_stats_units_nautical">Морская (морские мили, фут)</string>
<string name="select_show_on_map_behavior">Выберите формат данных для показа на карте</string>
<string name="show_on_map_format_ask">Спрашивать</string>
<string name="always">Запомнить</string>
<string name="voice_per_nautical_mile">за морскую милю</string>
<string name="settings_announcements_average_speed_pace">Средняя скорость/темп</string>
</resources> </resources>
-1
View File
@@ -517,7 +517,6 @@ limitations under the License.
<string name="settings_sensors_title">Senzory Bluetooth</string> <string name="settings_sensors_title">Senzory Bluetooth</string>
<string name="settings_sensors_summary">Cyklistické snímače, obvod kolies, snímače behu</string> <string name="settings_sensors_summary">Cyklistické snímače, obvod kolies, snímače behu</string>
<string name="settings_open_tracks_summary">Viac o projekte OpenTracks</string> <string name="settings_open_tracks_summary">Viac o projekte OpenTracks</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="settings_gps_summary">Interval času, interval vzdialenosti, Max. vzdialenosť, Presnosť</string> <string name="settings_gps_summary">Interval času, interval vzdialenosti, Max. vzdialenosť, Presnosť</string>
<string name="help_support_content"/> <string name="help_support_content"/>
<string name="share_image_body">Myslím, že by vás mohol zaujímať tento obrázok.</string> <string name="share_image_body">Myslím, že by vás mohol zaujímať tento obrázok.</string>
-1
View File
@@ -488,7 +488,6 @@ limitations under the License.
<string name="lap_time">Čas kroga</string> <string name="lap_time">Čas kroga</string>
<string name="track_detail_intervals_tab">Intervali</string> <string name="track_detail_intervals_tab">Intervali</string>
<string name="track_delete_not_recording">Te sledi ni mogoče izbrisati, saj je trenutno posneta.</string> <string name="track_delete_not_recording">Te sledi ni mogoče izbrisati, saj je trenutno posneta.</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="stats_clock">Ura</string> <string name="stats_clock">Ura</string>
<string name="settings_import_section">Uvoz</string> <string name="settings_import_section">Uvoz</string>
<string name="share_image_subject">Rad bi z vami delil sliko</string> <string name="share_image_subject">Rad bi z vami delil sliko</string>
-1
View File
@@ -431,7 +431,6 @@ limitations under the License.
<string name="generic_overwrite">Skriva</string> <string name="generic_overwrite">Skriva</string>
<string name="generic_separator_done_total">/</string> <string name="generic_separator_done_total">/</string>
<string name="export_track_errors">Spår som inte exporteras</string> <string name="export_track_errors">Spår som inte exporteras</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="help_data_storage_content"> Sensordata lagras per inspelad plats. När det gäller en pulssensor betyder det att endast <i>en</i> pulsmätning lagras per plats. Om du står stilla eller är inomhus, kommer inga sensordata att registreras (men det kommer att visas). </string> <string name="help_data_storage_content"> Sensordata lagras per inspelad plats. När det gäller en pulssensor betyder det att endast <i>en</i> pulsmätning lagras per plats. Om du står stilla eller är inomhus, kommer inga sensordata att registreras (men det kommer att visas). </string>
<string name="help_recording_title">Hur registrerar OpenTracks data\?</string> <string name="help_recording_title">Hur registrerar OpenTracks data\?</string>
<string name="about_description"><i>OpenTracks</i> är ett program för sportspårning som helt respekterar din integritet.</string> <string name="about_description"><i>OpenTracks</i> är ett program för sportspårning som helt respekterar din integritet.</string>
-1
View File
@@ -492,7 +492,6 @@ limitations under the License.
<string name="settings_prevent_reimport_tracks_title">Parçaların yeniden içe aktarılmasını önleme</string> <string name="settings_prevent_reimport_tracks_title">Parçaların yeniden içe aktarılmasını önleme</string>
<string name="stats_coordinates">Enlem/Boylam</string> <string name="stats_coordinates">Enlem/Boylam</string>
<string name="stats_sensors_power">Güç</string> <string name="stats_sensors_power">Güç</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="aggregated_stats_filter_no_results">Belirtilen filtre için etkinlik yok</string> <string name="aggregated_stats_filter_no_results">Belirtilen filtre için etkinlik yok</string>
<string name="value_float_kilometer_hour">%1$.1f km/s</string> <string name="value_float_kilometer_hour">%1$.1f km/s</string>
<string name="custom_layout_list_title">Dizilimler</string> <string name="custom_layout_list_title">Dizilimler</string>
-1
View File
@@ -472,7 +472,6 @@ limitations under the License.
<string name="track_pace_notification">Темп: %1$s</string> <string name="track_pace_notification">Темп: %1$s</string>
<string name="about_third_party_data">Дані третіх сторін</string> <string name="about_third_party_data">Дані третіх сторін</string>
<string name="delete_service">Служба видалення</string> <string name="delete_service">Служба видалення</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="track_delete_not_recording">Цей трек не може бути видалений в тому вигляді, в якому він записаний в даний момент.</string> <string name="track_delete_not_recording">Цей трек не може бути видалений в тому вигляді, в якому він записаний в даний момент.</string>
<string name="menu_voice_rate">Швидкість передачі голосу</string> <string name="menu_voice_rate">Швидкість передачі голосу</string>
<string name="settings_sensor_cycling_cadence">Велоспорт: частота обертання педалей</string> <string name="settings_sensor_cycling_cadence">Велоспорт: частота обертання педалей</string>
-1
View File
@@ -471,7 +471,6 @@ limitations under the License.
<string name="interval_list_empty_message">Không có bất kỳ khoảng thời gian sơ</string> <string name="interval_list_empty_message">Không có bất kỳ khoảng thời gian sơ</string>
<string name="aggregated_stats_empty_message">Hồ sơ của đua đầu tiên, để xem tổng thống kê</string> <string name="aggregated_stats_empty_message">Hồ sơ của đua đầu tiên, để xem tổng thống kê</string>
<string name="track_detail_intervals_tab">Khoảng thời gian</string> <string name="track_detail_intervals_tab">Khoảng thời gian</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="settings_recording_keep_screen_on_while_recording_title">Giữ trên màn hình</string> <string name="settings_recording_keep_screen_on_while_recording_title">Giữ trên màn hình</string>
<string name="bluetooth_disabled">Xin vui lòng cho phép Bluetooth.</string> <string name="bluetooth_disabled">Xin vui lòng cho phép Bluetooth.</string>
<string name="sensor_state_cadence_avg">Nhịp trung bình</string> <string name="sensor_state_cadence_avg">Nhịp trung bình</string>
+2
View File
@@ -46,6 +46,8 @@
<string name="show_on_map_format_key" translatable="false">showOnMapFormatKey</string> <string name="show_on_map_format_key" translatable="false">showOnMapFormatKey</string>
<string name="total_rows_deleted_key" translatable="false">totalRowsDeletedKey</string>
<string name="settings_default_export_directory_key" translatable="false">settingsDefaultExportDirectory</string> <string name="settings_default_export_directory_key" translatable="false">settingsDefaultExportDirectory</string>
<string name="post_workout_export_enabled_key" translatable="false">instantExportEnabled</string> <string name="post_workout_export_enabled_key" translatable="false">instantExportEnabled</string>
<bool name="post_workout_export_enabled_default" translatable="false">false</bool> <bool name="post_workout_export_enabled_default" translatable="false">false</bool>
+1 -1
View File
@@ -494,7 +494,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&#8230;</string> <string name="track_delete_progress_message">Deleting&#8230;</string>
<string name="track_delete_progress">%1$d/%2$d</string> <string name="track_delete_number_of_tracks">%1$d tracks</string>
<string name="track_delete_not_recording">This track cannot be deleted as it is currently recorded.</string> <string name="track_delete_not_recording">This track cannot be deleted as it is currently recorded.</string>
<string name="track_discarding">Discarding Track</string> <string name="track_discarding">Discarding Track</string>
<!-- Track Detail --> <!-- Track Detail -->