forked from upstream-mirrors/OpenTracks
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 45874d3749 | |||
| 594f69320a | |||
| d4927c9444 | |||
| 00dbd056d6 | |||
| 8f19ad8f66 | |||
| 2fdad54a7c | |||
| 5a5f040ed5 | |||
| 0ada9a2463 | |||
| 719bb981ec | |||
| 3c95d8744d | |||
| 63d1c9ee5e | |||
| 1ccd2e3c0a | |||
| cdbddabc1b | |||
| 9f6ca83f27 | |||
| f2ee99a477 | |||
| 7009ede095 | |||
| 2b530a1c53 | |||
| 33673f5921 |
+1
-1
@@ -2,4 +2,4 @@
|
||||
|
||||
liberapay: OpenTracks
|
||||
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
@@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
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 {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 4986
|
||||
versionName "v4.0.5"
|
||||
versionCode 5004
|
||||
versionName "v4.0.7"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -120,7 +120,9 @@ android {
|
||||
dependencies {
|
||||
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.documentfile:documentfile:1.0.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:runner:1.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'
|
||||
}
|
||||
|
||||
@@ -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.onView;
|
||||
import static androidx.test.espresso.Espresso.pressBack;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
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.withContentDescription;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.anything;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.childAtPosition;
|
||||
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.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.services.TrackDeleteService;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
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.action.ViewActions.click;
|
||||
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.isClickable;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
*/
|
||||
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.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -25,10 +29,6 @@ import org.junit.runner.RunWith;
|
||||
|
||||
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}.
|
||||
*
|
||||
|
||||
@@ -142,7 +142,7 @@ public class EGM2008UtilsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void data_Berlin() throws IOException {
|
||||
public void data_Berlin_Germany_() throws IOException {
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||
trackPoint.setLatitude(52.530644);
|
||||
@@ -157,7 +157,7 @@ public class EGM2008UtilsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void data_Berlin_Caching() throws IOException {
|
||||
public void data_Berlin_Germany_Caching() throws IOException {
|
||||
// given
|
||||
TrackPoint trackPoint1 = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||
trackPoint1.setLatitude(52.530644);
|
||||
@@ -176,6 +176,22 @@ public class EGM2008UtilsTest {
|
||||
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
|
||||
public void data_MaxUndulation() throws IOException {
|
||||
// given
|
||||
@@ -191,6 +207,7 @@ public class EGM2008UtilsTest {
|
||||
assertEquals(-85.824, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getIndices() {
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||
@@ -208,7 +225,7 @@ public class EGM2008UtilsTest {
|
||||
|
||||
trackPoint.setLatitude(-90);
|
||||
trackPoint.setLongitude(-180);
|
||||
assertEquals(new EGM2008Utils.Indices(2160, 0), EGM2008Utils.getIndices(trackPoint.getLocation()));
|
||||
assertEquals(new EGM2008Utils.Indices(2160, 2160), EGM2008Utils.getIndices(trackPoint.getLocation()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -59,6 +59,8 @@ limitations under the License.
|
||||
tools:targetApi="s" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<application
|
||||
android:name=".Startup"
|
||||
android:allowBackup="false"
|
||||
|
||||
@@ -84,8 +84,8 @@ public abstract class AbstractTrackDeleteActivity extends AbstractActivity imple
|
||||
/**
|
||||
* Called every time a track is deleted.
|
||||
*/
|
||||
protected void onTrackDeleteStatus(TrackDeleteService.DeleteStatus deleteStatus) {
|
||||
if (deleteStatus.isFinished() && trackDeleteServiceConnection != null) {
|
||||
protected void onTrackDeleteStatus(TrackDeleteService.DeletionFinishedStatus deletionFinishedStatus) {
|
||||
if (trackDeleteServiceConnection != null) {
|
||||
trackDeleteServiceConnection.unbind(this);
|
||||
trackDeleteServiceConnection = null;
|
||||
onDeleteFinished();
|
||||
|
||||
@@ -3,19 +3,15 @@ package de.dennisguse.opentracks;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import de.dennisguse.opentracks.databinding.ActivityShowErrorBinding;
|
||||
import de.dennisguse.opentracks.databinding.TrackListBinding;
|
||||
|
||||
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.util.IntentDashboardUtils;
|
||||
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.TrackIconUtils;
|
||||
|
||||
@@ -241,7 +241,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
startActivity(newIntent);
|
||||
|
||||
connection.unbind(this);
|
||||
}).startAndBind(this);
|
||||
}).startAndBind(this, true);
|
||||
});
|
||||
viewBinding.trackListFabAction.setOnLongClickListener((view) -> {
|
||||
if (!recordingStatus.isRecording()) {
|
||||
@@ -263,13 +263,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
}
|
||||
|
||||
private void requestRequiredPermissions() {
|
||||
if (!PermissionUtils.hasGPSPermission(this)) {
|
||||
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);
|
||||
}
|
||||
PermissionRequester.ALL.requestPermissionsIfNeeded(this, this, null, (requester) -> Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
@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.SearchAutoComplete searchAutoComplete = searchView.findViewById(R.id.search_src_text);
|
||||
searchAutoComplete.setHintTextColor(getResources().getColor(android.R.color.white));
|
||||
searchAutoComplete.setTextColor(getResources().getColor(android.R.color.white));
|
||||
searchAutoComplete.setHintTextColor(ContextCompat.getColor(this, android.R.color.white));
|
||||
searchAutoComplete.setTextColor(ContextCompat.getColor(this, android.R.color.white));
|
||||
|
||||
searchView.setOnCloseListener(() -> {
|
||||
searchView.clearFocus();
|
||||
|
||||
@@ -213,7 +213,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
|
||||
connection.unbind(this);
|
||||
finish();
|
||||
}).startAndBind(this);
|
||||
}).startAndBind(this, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -232,9 +232,9 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTrackDeleteStatus(TrackDeleteService.DeleteStatus deleteStatus) {
|
||||
super.onTrackDeleteStatus(deleteStatus);
|
||||
if (deleteStatus.isDeleted(trackId)) {
|
||||
protected void onTrackDeleteStatus(TrackDeleteService.DeletionFinishedStatus deletionFinishedStatus) {
|
||||
super.onTrackDeleteStatus(deletionFinishedStatus);
|
||||
if (deletionFinishedStatus.isDeleted(trackId)) {
|
||||
runOnUiThread(this::finish);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.view.WindowManager;
|
||||
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.util.IntentDashboardUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
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.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) -> {
|
||||
((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(1000);
|
||||
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this);
|
||||
|
||||
@@ -145,7 +145,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
|
||||
|
||||
connection.unbind(this);
|
||||
finish();
|
||||
}).startAndBind(this);
|
||||
}).startAndBind(this, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -32,9 +32,7 @@ import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
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.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.ChartBinding;
|
||||
|
||||
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.chart;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Altitude;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.graphics.Paint.Style;
|
||||
import android.graphics.Path;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
@@ -68,12 +69,12 @@ abstract class ChartValueSeries {
|
||||
|
||||
fillPaint = new Paint();
|
||||
fillPaint.setStyle(Style.FILL);
|
||||
fillPaint.setColor(context.getResources().getColor(fillColor));
|
||||
fillPaint.setColor(ContextCompat.getColor(context, fillColor));
|
||||
fillPaint.setAntiAlias(true);
|
||||
|
||||
strokePaint = new Paint();
|
||||
strokePaint.setStyle(Style.STROKE);
|
||||
strokePaint.setColor(context.getResources().getColor(strokeColor));
|
||||
strokePaint.setColor(ContextCompat.getColor(context, strokeColor));
|
||||
strokePaint.setAntiAlias(true);
|
||||
|
||||
// Make copies of the stroke paint with the default thickness
|
||||
|
||||
@@ -187,7 +187,6 @@ public class ContentProviderUtils {
|
||||
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(), "?")));
|
||||
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) {
|
||||
// Delete track folder resources.
|
||||
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())});
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
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.TrackPointsColumns;
|
||||
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.
|
||||
@@ -53,6 +55,8 @@ public class CustomContentProvider extends ContentProvider {
|
||||
|
||||
private static final String SQL_LIST_DELIMITER = ",";
|
||||
|
||||
private static final int TOTAL_DELETED_ROWS_VACUUM_THRESHOLD = 10000;
|
||||
|
||||
private final UriMatcher uriMatcher;
|
||||
|
||||
private SQLiteDatabase db;
|
||||
@@ -131,14 +135,12 @@ public class CustomContentProvider extends ContentProvider {
|
||||
@Override
|
||||
public int delete(@NonNull Uri url, String where, String[] selectionArgs) {
|
||||
String table;
|
||||
boolean shouldVacuum = false;
|
||||
switch (getUrlType(url)) {
|
||||
case TRACKPOINTS:
|
||||
table = TrackPointsColumns.TABLE_NAME;
|
||||
break;
|
||||
case TRACKS:
|
||||
table = TracksColumns.TABLE_NAME;
|
||||
shouldVacuum = true;
|
||||
break;
|
||||
case MARKERS:
|
||||
table = MarkerColumns.TABLE_NAME;
|
||||
@@ -147,23 +149,40 @@ public class CustomContentProvider extends ContentProvider {
|
||||
throw new IllegalArgumentException("Unknown URL " + url);
|
||||
}
|
||||
|
||||
Log.w(TAG, "Deleting table " + table);
|
||||
int count;
|
||||
Log.w(TAG, "Deleting from table " + table);
|
||||
int totalChangesBefore = getTotalChanges();
|
||||
int deletedRowsFromTable;
|
||||
try {
|
||||
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();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
getContext().getContentResolver().notifyChange(url, null, false);
|
||||
|
||||
if (shouldVacuum) {
|
||||
// If a potentially large amount of data was deleted, reclaim its space.
|
||||
Log.i(TAG, "Vacuuming the database.");
|
||||
int totalChanges = getTotalChanges() - totalChangesBefore;
|
||||
Log.i(TAG, "Deleted " + totalChanges + " total rows from 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");
|
||||
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
|
||||
|
||||
@@ -32,7 +32,6 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
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.TrackPoint;
|
||||
import de.dennisguse.opentracks.data.tables.MarkerColumns;
|
||||
|
||||
+1
-1
@@ -17,8 +17,8 @@ import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportActivity;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
|
||||
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.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
|
||||
@@ -290,4 +291,5 @@ public class TrackImporter {
|
||||
public void cleanImport() {
|
||||
contentProviderUtils.deleteTracks(context, trackIds);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public abstract class AbstractAPIActivity extends AppCompatActivity {
|
||||
if (PreferencesUtils.isPublicAPIenabled()) {
|
||||
Log.i(TAG, "Received and trying to execute requested action.");
|
||||
new TrackRecordingServiceConnection(serviceConnectedCallback)
|
||||
.startAndBind(this);
|
||||
.startAndBind(this, isStartServiceForeground());
|
||||
} else {
|
||||
Toast.makeText(this, getString(R.string.settings_public_api_disabled_toast), Toast.LENGTH_LONG).show();
|
||||
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 boolean isPostExecuteStopService();
|
||||
|
||||
@@ -57,4 +57,9 @@ public class StartRecording extends AbstractAPIActivity {
|
||||
protected boolean isPostExecuteStopService() {
|
||||
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.SensorDataSet;
|
||||
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.
|
||||
@@ -162,7 +162,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
||||
} else {
|
||||
connectionManager.disconnect();
|
||||
}
|
||||
if (!PermissionUtils.hasBluetoothPermissions(context)) {
|
||||
if (!PermissionRequester.BLUETOOTH.hasPermission(context)) {
|
||||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
public class TrackDeleteService extends Service {
|
||||
|
||||
@@ -31,7 +33,7 @@ public class TrackDeleteService extends Service {
|
||||
|
||||
private final Binder binder = new Binder();
|
||||
private ExecutorService serviceExecutor;
|
||||
private MutableLiveData<DeleteStatus> deleteResultObservable;
|
||||
private MutableLiveData<DeletionFinishedStatus> deleteResultObservable;
|
||||
private NotificationManager notificationManager;
|
||||
private NotificationCompat.Builder notificationBuilder;
|
||||
|
||||
@@ -59,22 +61,18 @@ public class TrackDeleteService extends Service {
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
private void deleteTracks(@NonNull ArrayList<Track.Id> trackIds) {
|
||||
sendResult(null,0, trackIds.size());
|
||||
private void deleteTracks(@NonNull List<Track.Id> trackIds) {
|
||||
serviceExecutor.execute(() -> {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
||||
for (int i = 0; i < trackIds.size(); i++) {
|
||||
updateNotification(i + 1, trackIds.size());
|
||||
contentProviderUtils.deleteTrack(this, trackIds.get(i));
|
||||
sendResult(trackIds.get(i), i + 1, trackIds.size());
|
||||
}
|
||||
contentProviderUtils.deleteTracks(this, trackIds);
|
||||
sendResult(trackIds);
|
||||
stopSelf();
|
||||
});
|
||||
}
|
||||
|
||||
private void sendResult(Track.Id trackId, int deletes, int total) {
|
||||
private void sendResult(List<Track.Id> trackIds) {
|
||||
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;
|
||||
}
|
||||
|
||||
public LiveData<DeleteStatus> getDeletingStatusObservable() {
|
||||
public LiveData<DeletionFinishedStatus> getDeletingStatusObservable() {
|
||||
return deleteResultObservable;
|
||||
}
|
||||
|
||||
@@ -106,25 +104,13 @@ public class TrackDeleteService extends Service {
|
||||
notificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
|
||||
notificationBuilder
|
||||
.setContentTitle(this.getString(R.string.track_delete_progress_message))
|
||||
.setContentText(this.getString(R.string.track_delete_progress, 0, tracksToDelete))
|
||||
.setContentText(this.getString(R.string.track_delete_number_of_tracks, tracksToDelete))
|
||||
.setSmallIcon(R.drawable.ic_logo_color_24dp)
|
||||
.setProgress(tracksToDelete, 0, false);
|
||||
.setProgress(0, 0, true);
|
||||
|
||||
startForeground(NOTIFICATION_ID, notificationBuilder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates notification progress.
|
||||
*
|
||||
* @param progress number of tracks already deleted.
|
||||
* @param total total of tracks to be deleted.
|
||||
*/
|
||||
private void updateNotification(int progress, int total) {
|
||||
notificationBuilder.setProgress(total, progress, false);
|
||||
notificationBuilder.setContentText(this.getString(R.string.track_delete_progress, progress, total));
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
}
|
||||
|
||||
public class Binder extends android.os.Binder {
|
||||
|
||||
private Binder() {
|
||||
@@ -136,36 +122,25 @@ public class TrackDeleteService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
public static class DeleteStatus {
|
||||
private final int progress;
|
||||
private final int max;
|
||||
private final Track.Id trackId;
|
||||
public static class DeletionFinishedStatus {
|
||||
private final List<Track.Id> trackIds;
|
||||
|
||||
/**
|
||||
* @param trackId Track.Id just deleted if any.
|
||||
* @param progress number of deletes.
|
||||
* @param max total of deletes to be done.
|
||||
* @param trackIds List of deleted Track.Ids.
|
||||
*/
|
||||
private DeleteStatus(@Nullable Track.Id trackId, int progress, int max) {
|
||||
this.trackId = trackId;
|
||||
this.progress = progress;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return progress == max;
|
||||
private DeletionFinishedStatus(@Nullable List<Track.Id> trackIds) {
|
||||
this.trackIds = trackIds;
|
||||
}
|
||||
|
||||
public boolean isDeleted(Track.Id trackId) {
|
||||
return this.trackId != null && this.trackId.equals(trackId);
|
||||
return this.trackIds != null && this.trackIds.contains(trackId);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DeleteStatus{" +
|
||||
"number of deletes=" + progress +
|
||||
", total=" + max +
|
||||
"trackIds=" + trackIds +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
trackRecordingManager = null;
|
||||
|
||||
// Reverse order from onCreate
|
||||
showNotification(false); //TODO Why?
|
||||
stopForeground(true);
|
||||
|
||||
notificationManager.stop();
|
||||
notificationManager = null;
|
||||
|
||||
@@ -205,7 +206,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
private void startSensors() {
|
||||
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
|
||||
trackPointCreator.start(this, handler);
|
||||
showNotification(true);
|
||||
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this));
|
||||
}
|
||||
|
||||
public void endCurrentTrack() {
|
||||
@@ -247,7 +248,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
if (!isRecording()) return;
|
||||
|
||||
trackPointCreator.stop();
|
||||
showNotification(false);
|
||||
stopForeground(true);
|
||||
notificationManager.cancelNotification();
|
||||
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) {
|
||||
if (!isRecording()) {
|
||||
return null;
|
||||
|
||||
+10
-3
@@ -28,6 +28,7 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import de.dennisguse.opentracks.BuildConfig;
|
||||
import de.dennisguse.opentracks.R;
|
||||
@@ -66,15 +67,21 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// Service is already started and bound.
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -88,7 +95,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
@Deprecated
|
||||
public void startAndBindWithCallback(Context context) {
|
||||
if (trackRecordingService == null) {
|
||||
startAndBind(context);
|
||||
startAndBind(context, false);
|
||||
return;
|
||||
}
|
||||
if (callback != null) {
|
||||
|
||||
+1
@@ -68,6 +68,7 @@ class TrackRecordingServiceNotificationManager implements SharedPreferences.OnSh
|
||||
}
|
||||
|
||||
void stop() {
|
||||
cancelNotification();
|
||||
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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.shouldVoiceAnnounceLapHeartRate;
|
||||
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.shouldVoiceAnnounceTotalDistance;
|
||||
|
||||
import android.content.Context;
|
||||
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.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
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.
|
||||
public void onStop() {
|
||||
if (locationManager != null && context != null) {
|
||||
if (PermissionUtils.hasGPSPermission(context)) {
|
||||
if (PermissionRequester.GPS.hasPermission(context)) {
|
||||
locationManager.removeUpdates(this);
|
||||
}
|
||||
locationManager = null;
|
||||
@@ -152,7 +152,7 @@ public class GPSHandler implements LocationListener, GpsStatus.GpsStatusListener
|
||||
Log.e(TAG, "Not started.");
|
||||
return;
|
||||
}
|
||||
if (PermissionUtils.hasGPSPermission(context)) {
|
||||
if (PermissionRequester.GPS.hasPermission(context)) {
|
||||
try {
|
||||
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, gpsInterval.toMillis(), 0, this);
|
||||
} catch (SecurityException e) {
|
||||
|
||||
@@ -23,7 +23,7 @@ class GpsStatus {
|
||||
private static final String TAG = GpsStatus.class.getSimpleName();
|
||||
|
||||
// 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;
|
||||
// Threshold for time without points.
|
||||
|
||||
@@ -8,9 +8,6 @@ import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
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 {
|
||||
|
||||
|
||||
@@ -826,4 +826,17 @@ public class PreferencesUtils {
|
||||
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.R;
|
||||
import de.dennisguse.opentracks.databinding.SettingsBinding;
|
||||
import de.dennisguse.opentracks.databinding.TrackRecordedBinding;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
|
||||
public class SettingsActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller {
|
||||
|
||||
+13
-15
@@ -29,7 +29,7 @@ import java.util.stream.Collectors;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothUtils;
|
||||
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.
|
||||
@@ -145,20 +145,18 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
|
||||
bluetoothIcon = AnimatedVectorDrawableCompat.create(getContext(), R.drawable.ic_bluetooth_searching_animated_24dp);
|
||||
bluetoothIcon.start();
|
||||
|
||||
if (!PermissionUtils.hasBluetoothPermissions(getContext())) {
|
||||
PermissionUtils.requestBluetoothPermission(this,
|
||||
this::startBluetoothScan,
|
||||
() -> {
|
||||
if (PermissionUtils.shouldShowRequestPermissionRationaleBluetooth(this)) {
|
||||
Toast.makeText(getContext(), R.string.permission_bluetooth_failed_rejected, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(getContext(), R.string.permission_bluetooth_failed, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
dismiss();
|
||||
});
|
||||
} else {
|
||||
startBluetoothScan();
|
||||
}
|
||||
PermissionRequester.BLUETOOTH.requestPermissionsIfNeeded(getContext(), this,
|
||||
this::startBluetoothScan,
|
||||
(requester) -> {
|
||||
if (requester.shouldShowRequestPermissionRationale(this)) {
|
||||
Toast.makeText(getContext(), R.string.permission_bluetooth_failed_rejected, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(getContext(), R.string.permission_bluetooth_failed, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
dismiss();
|
||||
});
|
||||
|
||||
startBluetoothScan();
|
||||
}
|
||||
|
||||
private void startBluetoothScan() {
|
||||
|
||||
-1
@@ -5,7 +5,6 @@ import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.DatePicker;
|
||||
|
||||
@@ -22,6 +22,8 @@ import android.view.ViewGroup.LayoutParams;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@@ -169,7 +171,7 @@ public class ListItemUtils {
|
||||
textView.setVisibility(View.VISIBLE);
|
||||
textView.setText(value);
|
||||
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 {
|
||||
textView.setShadowLayer(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@@ -27,11 +27,11 @@ abstract public class RecyclerViewSwipeDeleteCallback extends ItemTouchHelper.Ca
|
||||
|
||||
public RecyclerViewSwipeDeleteCallback(Context context) {
|
||||
background = new ColorDrawable();
|
||||
backgroundColor = context.getResources().getColor(R.color.colorBackgroundDelete);
|
||||
backgroundColor = ContextCompat.getColor(context, R.color.colorBackgroundDelete);
|
||||
clearPaint = new Paint();
|
||||
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
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();
|
||||
intrinsicHeight = deleteDrawable.getIntrinsicHeight();
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class EGM2008Utils {
|
||||
if (location.getLongitude() >= 0) {
|
||||
longitude = location.getLongitude();
|
||||
} else {
|
||||
longitude = 180 + Math.abs(location.getLongitude());
|
||||
longitude = 360 + location.getLongitude();
|
||||
}
|
||||
int longitudeIndex = (int) (longitude * RESOLUTION_IN_MINUTES);
|
||||
|
||||
@@ -172,5 +172,13 @@ public class EGM2008Utils {
|
||||
public int hashCode() {
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -12,8 +13,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
--><resources>
|
||||
<!-- Activity Type -->
|
||||
<string name="activity_type_airplane">طائرة</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_metric">وحدات القياس المترية (كم، م)</string>
|
||||
<string name="settings_stats_units_title">الوحدات المفضلة</string>
|
||||
|
||||
<!-- 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>
|
||||
<!-- Stats -->
|
||||
<string name="stats_average_moving_pace">متوسط إيقاع التحرك</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_altitude">الارتفاع</string>
|
||||
<string name="stats_fastest_pace">أسرع إيقاع</string>
|
||||
@@ -352,4 +353,4 @@ limitations under the License.
|
||||
<string name="waypoint_type_waterfall">شلال</string>
|
||||
<string name="waypoint_type_water_fountain">نافورة مياه</string>
|
||||
<string name="waypoint_type_waypoint">نقطة طريق</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -411,7 +411,6 @@
|
||||
<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_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="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>
|
||||
|
||||
@@ -458,7 +458,6 @@ limitations under the License.
|
||||
<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="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="about_third_party_data">Данни от трети страни</string>
|
||||
<string name="import_unsupported_format">Неподдържан формат на файла</string>
|
||||
|
||||
@@ -469,7 +469,6 @@ limitations under the License.
|
||||
<string name="track_speed_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="track_delete_progress">%1$d/%2$d</string>
|
||||
<string name="delete_service">Služba mazání</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>
|
||||
|
||||
@@ -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="about_third_party_data">Data fra tredjeparter</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="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>
|
||||
|
||||
@@ -276,9 +276,8 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
|
||||
\n%1$s</string>
|
||||
<string name="share_track_subject">Ich möchte einen Track mit dir teilen</string>
|
||||
<!-- Stats -->
|
||||
<string name="stats_average_moving_pace">Durchschnitt\n(Bewegung)</string>
|
||||
<string name="stats_average_moving_speed">Durchschnittsgeschwindigkeit
|
||||
\n(Bewegung)</string>
|
||||
<string name="stats_average_moving_pace">Durchschnittliches Bewegungstempo</string>
|
||||
<string name="stats_average_moving_speed">Mittlere Geschwindigkeit in Bewegung</string>
|
||||
<string name="stats_average_pace">Durchschnitt</string>
|
||||
<string name="stats_average_speed">Durchschnittsgeschwindigkeit</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="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="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="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="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="track_delete_progress">%1$d/%2$d</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="delete_service">Löschdienst</string>
|
||||
|
||||
@@ -464,7 +464,6 @@ limitations under the License.
|
||||
<string name="track_pace_notification">Ritmo: %1$s</string>
|
||||
<string name="about_third_party_data">Datos de terceros</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="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>
|
||||
|
||||
@@ -464,7 +464,6 @@ limitations under the License.
|
||||
<string name="track_pace_notification">Tempo: %1$s</string>
|
||||
<string name="about_third_party_data">Kolmanda osapoole andmed</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="track_delete_not_recording">Seda pala ei saa kustutada, kuna see on praegu salvestatud.</string>
|
||||
<string name="settings_sensor_cycling_cadence">Rütm</string>
|
||||
|
||||
@@ -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="about_third_party_data">Hirugarrenen datuak</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="track_delete_not_recording">Pista hau ezin da ezabatu da gaur egun grabatzen.</string>
|
||||
<string name="settings_sensor_cycling_cadence">Kadentzia</string>
|
||||
|
||||
@@ -469,7 +469,6 @@ limitations under the License.
|
||||
<string name="track_speed_notification">Nopeus: %1$s</string>
|
||||
<string name="about_third_party_data">Kolmannen osapuolen tiedot</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="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>
|
||||
|
||||
@@ -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_progress_summary_overwrite_msg">Traces écrasées</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="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>
|
||||
|
||||
@@ -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="about_third_party_data">Datos de terceiros alleos</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="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>
|
||||
|
||||
@@ -460,7 +460,6 @@ limitations under the License.
|
||||
\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="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="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>
|
||||
|
||||
@@ -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="about_third_party_data">Dati di terzi</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="track_delete_not_recording">Questa traccia non può essere cancellata perché è attualmente in registrazione.</string>
|
||||
<string name="settings_sensor_cycling_cadence">Cadenza</string>
|
||||
|
||||
@@ -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="generic_open_track">Atidaryti takelį</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="help_gps_troubleshooting_title">OpenTracks nefiksuoja vietų\?</string>
|
||||
<string name="generic_overwrite">Perrašyti</string>
|
||||
|
||||
@@ -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="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="track_delete_progress">%1$d/%2$d</string>
|
||||
<string name="sensor_state_power_avg">Vidējā jauda</string>
|
||||
<string name="about_libraries">Bibliotēkas</string>
|
||||
<string name="description_default_speed_or_pace">Pēc aktivitātes veida</string>
|
||||
|
||||
@@ -460,7 +460,6 @@ limitations under the License.
|
||||
<string name="track_pace_notification">Tempo: %1$s</string>
|
||||
<string name="about_third_party_data">Tredjepartsdata</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="track_delete_not_recording">Dette sporet kan ikke slettes da det tas opp nå.</string>
|
||||
<string name="settings_sensor_cycling_cadence">Kadens</string>
|
||||
|
||||
@@ -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_pace_notification">Tempo: %1$s</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="menu_voice_rate">Spraak snelheid</string>
|
||||
<string name="track_delete_not_recording">Dit fragment kan niet worden gewist omdat het momenteel is opgenomen.</string>
|
||||
|
||||
@@ -476,7 +476,6 @@ limitations under the License.
|
||||
<string name="track_distance_notification">Odległość: %1$s</string>
|
||||
<string name="about_third_party_data">Dane osób trzecich</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="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>
|
||||
|
||||
@@ -404,7 +404,6 @@
|
||||
<string name="track_pace_notification">Parcela: %1$s</string>
|
||||
<string name="about_third_party_data">Dados de terceiros</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="track_delete_not_recording">Esta trilha não pode ser excluída porque está gravada no momento.</string>
|
||||
<plurals name="voiceSpeedKilometersPerHour">
|
||||
|
||||
@@ -426,7 +426,6 @@ limitations under the License.
|
||||
<string name="import_progress_summary_exists_msg">Există deja</string>
|
||||
<string name="track_detail_intervals_tab">Intervaluri</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_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>
|
||||
|
||||
@@ -24,7 +24,7 @@ limitations under the License.
|
||||
<string name="activity_type_commercial_airplane">Пассажирские самолеты</string>
|
||||
<string name="activity_type_cross_country_skiing">лыжные гонки</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_driving">на автомобиле</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_helicopter">Радиоуправляемые вертолеты</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_rowing">Гребля академическая</string>
|
||||
<string name="activity_type_running">Бег</string>
|
||||
@@ -128,7 +128,7 @@ limitations under the License.
|
||||
<string name="image_arrow">Стрелка</string>
|
||||
<string name="image_marker">Маркер</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_stop">Стоп</string>
|
||||
<string name="image_track">Трек</string>
|
||||
@@ -212,7 +212,7 @@ limitations under the License.
|
||||
<string name="stats_average_speed">Средняя скорость</string>
|
||||
<string name="stats_altitude">Высота</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_longitude">Долгота</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_theater">Театр</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_tunnel">Тоннель</string>
|
||||
<string name="waypoint_type_waterfall">Водопад</string>
|
||||
<string name="waypoint_type_water_fountain">Фонтан</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>.
|
||||
\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>
|
||||
<string name="help_storage_pictures_title">Как OpenTracks хранит изображения, сделанные в качестве маркеров\?</string>
|
||||
<string name="about_preference_title">О OpenTracks</string>
|
||||
<string name="about_description"><i>OpenTracks</i> - это приложение для отслеживания спортивных событий, которое полностью уважает вашу конфиденциальность.</string>
|
||||
<string name="about_preference_title">Об OpenTracks</string>
|
||||
<string name="about_description"><i>OpenTracks</i> — это приложение для отслеживания спортивных событий, которое полностью уважает вашу конфиденциальность.</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_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="help_feature_title">OpenTracks имеет следующие возможности:</string>
|
||||
<string name="help_feature_content">* создание GPS-треков
|
||||
\n* использование датчика частоты пульса (только Bluetooth LE)
|
||||
\n* голосовые объявления (по времени или расстоянию)
|
||||
\n* создание маркеров (в т.ч. картинки)
|
||||
\n* голосовые повещения (по времени или расстоянию)
|
||||
\n* создание маркеров (в т.ч. с изображениями)
|
||||
\n* работа полностью офлайн
|
||||
\n* показ треков на карте (см. ниже)</string>
|
||||
<string name="help_track_map_title">Как показать трек на карте\?</string>
|
||||
<string name="help_storage_pictures_content">Они хранятся во внутренней памяти приложения (т. е. доступны только в OpenTracks).</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_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="export_progress_summary_new_msg">Новые экспортируемые треки</string>
|
||||
<string name="export_progress_summary_new_msg">Новых треков экспортировано</string>
|
||||
<string name="help_data_storage_title">Как OpenTracks записывает данные с датчиков\?</string>
|
||||
<string name="export_track_already_exists_msg">Дорожка %1$s уже существует в каталоге назначения.</string>
|
||||
<string name="export_without_photos">без фотографий</string>
|
||||
<string name="export_with_photos">с фотографией</string>
|
||||
<string name="description_default_speed_or_pace">По типу деятельности</string>
|
||||
<string name="export_all_do_it_for_all">делать это во всех конфликтах</string>
|
||||
<string name="stats_loss">Потери</string>
|
||||
<string name="export_progress_summary_skip_msg">Пропущенные дорожки</string>
|
||||
<string name="generic_click_twice_cancel">Нажмите назад, чтобы отменить</string>
|
||||
<string name="export_all_do_it_for_all">для всех конфликтов</string>
|
||||
<string name="stats_loss">Спуск</string>
|
||||
<string name="export_progress_summary_skip_msg">Пропущенные треки</string>
|
||||
<string name="generic_click_twice_cancel">Нажмите назад еще раз, чтобы отменить</string>
|
||||
<string name="generic_completed">Завершено</string>
|
||||
<string name="generic_show_errors">Показать ошибки</string>
|
||||
<string name="generic_separator_done_total">/</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="import_file_imported">Файл %1$s импортирован</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="help_support_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="settings_prevent_reimport_tracks_title">Предотвращение повторного импорта треков</string>
|
||||
<string name="sensor_state_cadence_max">Макс. каденция</string>
|
||||
@@ -420,33 +420,33 @@ limitations under the License.
|
||||
<string name="bluetooth_disabled">Включите Bluetooth.</string>
|
||||
<string name="sensor_state_power_avg">Средняя мощность</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_night_mode_option_night">Ночная</string>
|
||||
<string name="settings_night_mode_option_day">Дневная</string>
|
||||
<string name="export_progress_summary_overwrite_msg">Перезапись дорожек</string>
|
||||
<string name="export_track_errors">Треки не экспортируются</string>
|
||||
<string name="export_progress_summary_overwrite_msg">Перезапись треков</string>
|
||||
<string name="export_track_errors">Треки не экспортированы</string>
|
||||
<string name="export_error_post_workout">Сбой экспорта после тренировки, пожалуйста, проверьте каталог экспорта.</string>
|
||||
<string name="description_altitude_loss">Потеря высоты: %1$d м (%2$d футов)</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="track_detail_intervals_tab">Интервалы</string>
|
||||
<string name="help_data_storage_content"> Данные датчика хранятся для каждой записи. Для датчика пульса это означает, что для каждой записи сохраняется только <i>одно</i> измерение пульса. Если вы стоите на месте или находитесь в помещении, данные датчика не будут записаны (но будут отображаться). </string>
|
||||
<string name="help_gps_troubleshooting_content"> Что вы можете сделать:
|
||||
<string name="help_data_storage_content"> Данные датчиков сохраняются для каждого местоположения. Для датчика пульса это означает, что для местоположения сохраняется только <i>одно</i> значение пульса. Если вы стоите на месте или находитесь в помещении, данные датчика не будут записываться (но будут отображаться). </string>
|
||||
<string name="help_gps_troubleshooting_content"> Что можно сделать:
|
||||
\n a) проверить, включен ли GPS,
|
||||
\n б) улучшить прием GPS (например, выехать на природу),
|
||||
\n б) улучшить прием GPS (например, выйти из помещения),
|
||||
\n c) проверить с помощью приложения <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a>, и
|
||||
\n г) отключите энергосбережение.
|
||||
\n г) отключть энергосбережение.
|
||||
\n
|
||||
\n Если все это не поможет, пожалуйста, сообщите об ошибке. </string>
|
||||
<string name="track_pace_notification">Темп: %1$s</string>
|
||||
<string name="track_speed_notification">Скорость: %1$s</string>
|
||||
<string name="track_distance_notification">Расстояние: %1$s</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="hold_to_stop">Удерживать для остановки</string>
|
||||
<string name="hold_to_stop">Удерживайте для остановки</string>
|
||||
<string name="share_image_body">Я думаю, вас может заинтересовать это изображение.</string>
|
||||
<string name="help_support_content"> Вы можете:
|
||||
\n a) распространять информацию об OpenTracks,
|
||||
@@ -463,12 +463,11 @@ limitations under the License.
|
||||
<string name="sensor_could_not_scan">Не удалось выполнить сканирование устройств Bluetooth (ошибка %1$i).</string>
|
||||
<string name="import_progress_summary_errors_msg">Ошибки</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="track_delete_progress">%1$d/%2$d</string>
|
||||
<string name="about_third_party_data">Сторонние данные</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">
|
||||
<item quantity="one">Завершена с %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_distance_speed">Дистанция и Скорость</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_open">Плавание на открытой воде</string>
|
||||
<string name="activity_type_workout">Тренировка</string>
|
||||
@@ -523,7 +522,7 @@ limitations under the License.
|
||||
</plurals>
|
||||
<string name="settings_layout_reset">Сбросить свои раскладки</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_yesterday">Вчера</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_message">Настройки всех раскладок будут сброшены на значения по умолчанию. Это не удалит треки на устройстве.</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="voice_per_kilometer">за километр</string>
|
||||
<string name="voice_per_mile">за милю</string>
|
||||
@@ -555,7 +554,7 @@ limitations under the License.
|
||||
<string name="average_heart_rate">Средний пульс</string>
|
||||
<string name="current_heart_rate">Текущий пульс</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_import_section">Импорт</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="share_crash_report">Поделиться отчётом о вылете</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>
|
||||
@@ -517,7 +517,6 @@ limitations under the License.
|
||||
<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_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="help_support_content"/>
|
||||
<string name="share_image_body">Myslím, že by vás mohol zaujímať tento obrázok.</string>
|
||||
|
||||
@@ -488,7 +488,6 @@ limitations under the License.
|
||||
<string name="lap_time">Čas kroga</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_progress">%1$d/%2$d</string>
|
||||
<string name="stats_clock">Ura</string>
|
||||
<string name="settings_import_section">Uvoz</string>
|
||||
<string name="share_image_subject">Rad bi z vami delil sliko</string>
|
||||
|
||||
@@ -431,7 +431,6 @@ limitations under the License.
|
||||
<string name="generic_overwrite">Skriva</string>
|
||||
<string name="generic_separator_done_total">/</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_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>
|
||||
|
||||
@@ -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="stats_coordinates">Enlem/Boylam</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="value_float_kilometer_hour">%1$.1f km/s</string>
|
||||
<string name="custom_layout_list_title">Dizilimler</string>
|
||||
|
||||
@@ -472,7 +472,6 @@ limitations under the License.
|
||||
<string name="track_pace_notification">Темп: %1$s</string>
|
||||
<string name="about_third_party_data">Дані третіх сторін</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="menu_voice_rate">Швидкість передачі голосу</string>
|
||||
<string name="settings_sensor_cycling_cadence">Велоспорт: частота обертання педалей</string>
|
||||
|
||||
@@ -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="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_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="bluetooth_disabled">Xin vui lòng cho phép Bluetooth.</string>
|
||||
<string name="sensor_state_cadence_avg">Nhịp trung bình</string>
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
|
||||
<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="post_workout_export_enabled_key" translatable="false">instantExportEnabled</string>
|
||||
<bool name="post_workout_export_enabled_default" translatable="false">false</bool>
|
||||
|
||||
@@ -494,7 +494,7 @@ limitations under the License.
|
||||
<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_progress_message">Deleting…</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_discarding">Discarding Track</string>
|
||||
<!-- Track Detail -->
|
||||
|
||||
Reference in New Issue
Block a user