Compare commits

...

13 Commits

Author SHA1 Message Date
Dennis Guse c4235b1961 Release: v4.7.1 2023-08-06 18:26:58 +02:00
josé m 2b73139051 Translated using Weblate (Galician)
Currently translated at 8.8% (10 of 113 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/gl/
2023-08-06 12:44:33 +02:00
Skrripy 1a2d263945 Translated using Weblate (Ukrainian)
Currently translated at 83.5% (436 of 522 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/uk/
2023-08-06 12:44:33 +02:00
Leon Marz e69bea802f AggregatedStatisticsActivity: Convert ListView to RecyclerView
Part of: #1613
2023-08-06 12:44:29 +02:00
Dennis Guse 4baaff8374 TrackDeleteService can be a JobIntentService (rather than ForegroundService).
Android 14 requires ForegroundService to have a reason.

Fixes  #1657.
2023-08-06 11:41:40 +02:00
Dennis Guse 1ed0a11c2f Move ExportServiceResultReceiver.java into ExportService. 2023-08-06 11:15:48 +02:00
Dennis Guse 971b0196da Android14: crash due to missing announcement of ForegroundServiceType.
Fixes #1655.
2023-08-06 10:51:25 +02:00
Dennis Guse 45f41d6401 Bugfix: Android 14's AltitudeConverter throws exception if no geo coords are present. 2023-08-06 10:38:12 +02:00
Dennis Guse 42234515cb Android14: crash due to missing announcement of ForegroundServiceType.
Fixes #1655.
2023-08-06 09:49:17 +02:00
Skrripy 10e479ceb9 Translated using Weblate (Ukrainian)
Currently translated at 83.3% (435 of 522 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/uk/
2023-08-04 08:11:20 +02:00
Skrripy 88d8a808f3 Translated using Weblate (Ukrainian)
Currently translated at 82.7% (432 of 522 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/uk/
2023-08-04 08:11:20 +02:00
josé m f15ae48846 Translated using Weblate (Galician)
Currently translated at 100.0% (522 of 522 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/gl/
2023-08-04 08:11:20 +02:00
Milo Ivir 5bf4cef57d Translated using Weblate (Croatian)
Currently translated at 57.2% (299 of 522 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/hr/
2023-08-04 08:11:20 +02:00
24 changed files with 280 additions and 405 deletions
+2 -2
View File
@@ -64,8 +64,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 5313
versionName "v4.7.0"
versionCode 5326
versionName "v4.7.1"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -0,0 +1,8 @@
v4.7.1: OpenTracks
Bugfixes:
- Android 14: crash on recording start (missing permissions)
- Android 14: crash on track deletion (ForegroundService without reason)
Developer:
- AggregatedStats is now a RecyclerView
@@ -0,0 +1,8 @@
v4.6.2: OpenTracks
Arranxos:
- Fallo da app cos anuncios por voz para imperial+métrico
- Fallo nos axustes para imperial+métrico
- Arranxo da inconsistencia nas gráficas (actualización Material3)
- Perda do permiso Bluetooth
- Estatítiscas acumuladas da velocidade media non mostraban unidades imperiais
@@ -0,0 +1,9 @@
v4.7.0 OpenTracks
Cambios:
- Estatísticas: inclúese distancia, tempo total e tempo en movemento entre os segmentos automáticos (creado debido a que a distancia á localización anterior era demasiado grande)
- As rutas gravadas volven a mostrar estatísticas do sensor outra vez
Arranxos:
- GPX exporter: para os segmentos automáticos que poderían non ter localización
- CSV exporter: a distancia non usaba o agrupamento con decimal
+4 -3
View File
@@ -41,6 +41,9 @@ limitations under the License.
android:required="false" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.VIBRATE" />
@@ -407,10 +410,8 @@ limitations under the License.
<service
android:name=".services.TrackDeleteService"
android:enabled="true"
android:exported="false"
android:icon="@drawable/ic_logo_color_24dp"
android:label="@string/delete_service" />
android:permission="android.permission.BIND_JOB_SERVICE"/>
</application>
<queries>
@@ -16,6 +16,7 @@
package de.dennisguse.opentracks;
import android.os.Handler;
import android.widget.Toast;
import java.util.ArrayList;
@@ -24,7 +25,6 @@ import java.util.stream.Collectors;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.services.TrackDeleteService;
import de.dennisguse.opentracks.services.TrackDeleteServiceConnection;
import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment.ConfirmDeleteCaller;
@@ -37,31 +37,8 @@ import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragm
* @author Jimmy Shih
*/
//TODO Check if this class is still such a good idea; inheritance might limit maintainability
public abstract class AbstractTrackDeleteActivity extends AbstractActivity implements ConfirmDeleteCaller, TrackDeleteServiceConnection.Listener {
public abstract class AbstractTrackDeleteActivity extends AbstractActivity implements ConfirmDeleteCaller, TrackDeleteService.TrackDeleteResultReceiver.Receiver {
private TrackDeleteServiceConnection trackDeleteServiceConnection;
@Override
protected void onStart() {
super.onStart();
trackDeleteServiceConnection = new TrackDeleteServiceConnection(this);
trackDeleteServiceConnection.bind(this);
}
@Override
protected void onStop() {
super.onStop();
if (trackDeleteServiceConnection != null) {
trackDeleteServiceConnection.unbind(this);
trackDeleteServiceConnection = null;
}
}
/**
* Delete tracks.
*
* @param trackIds the track ids
*/
protected void deleteTracks(Track.Id... trackIds) {
ConfirmDeleteDialogFragment.showDialog(getSupportFragmentManager(), trackIds);
}
@@ -77,29 +54,10 @@ public abstract class AbstractTrackDeleteActivity extends AbstractActivity imple
Toast.makeText(this, getString(R.string.track_delete_not_recording), Toast.LENGTH_LONG).show();
}
trackDeleteServiceConnection = new TrackDeleteServiceConnection(this);
trackDeleteServiceConnection.startAndBind(this, trackIdList);
}
/**
* Called every time a track is deleted.
*/
protected void onTrackDeleteStatus(TrackDeleteService.DeletionFinishedStatus deletionFinishedStatus) {
if (trackDeleteServiceConnection != null) {
trackDeleteServiceConnection.unbind(this);
trackDeleteServiceConnection = null;
onDeleteFinished();
}
TrackDeleteService.enqueue(this, new TrackDeleteService.TrackDeleteResultReceiver(new Handler(), this), trackIdList);
}
protected abstract void onDeleteConfirmed();
protected abstract void onDeleteFinished();
protected abstract Track.Id getRecordingTrackId();
@Override
public void connected(TrackDeleteService service) {
service.getDeletingStatusObservable().observe(AbstractTrackDeleteActivity.this, this::onTrackDeleteStatus);
}
}
@@ -422,7 +422,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
}
@Override
protected void onDeleteFinished() {
public void onDeleteFinished() {
// Do nothing
}
@@ -40,7 +40,6 @@ import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.databinding.TrackRecordedBinding;
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
import de.dennisguse.opentracks.services.RecordingStatus;
import de.dennisguse.opentracks.services.TrackDeleteService;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.SettingsActivity;
@@ -230,22 +229,13 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
return recordingStatus.getTrackId();
}
@Override
protected void onTrackDeleteStatus(TrackDeleteService.DeletionFinishedStatus deletionFinishedStatus) {
super.onTrackDeleteStatus(deletionFinishedStatus);
if (deletionFinishedStatus.isDeleted(trackId)) {
runOnUiThread(this::finish);
}
}
@Override
protected void onDeleteConfirmed() {
runOnUiThread(this::finish);
}
@Override
protected void onDeleteFinished() {
// Do nothing
public void onDeleteFinished() {
runOnUiThread(this::finish);
}
/**
@@ -158,7 +158,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
}
@Override
protected void onDeleteFinished() {
public void onDeleteFinished() {
finish();
}
@@ -56,7 +56,7 @@ import de.dennisguse.opentracks.util.FileUtils;
* So, for this check actually a different file name might be used than in the ExportService.
* * Saved state as an object instead of individual values.
*/
public class ExportActivity extends FragmentActivity implements ExportServiceResultReceiver.Receiver {
public class ExportActivity extends FragmentActivity implements ExportService.ExportServiceResultReceiver.Receiver {
private static final String TAG = ExportActivity.class.getSimpleName();
@@ -84,7 +84,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
private TrackFileFormat trackFileFormat;
private Uri directoryUri;
private ExportServiceResultReceiver resultReceiver;
private ExportService.ExportServiceResultReceiver resultReceiver;
private List<String> directoryFiles;
@@ -161,7 +161,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
DocumentFile documentFile = DocumentFile.fromTreeUri(this, directoryUri);
String directoryDisplayName = FileUtils.getPath(documentFile);
resultReceiver = new ExportServiceResultReceiver(new Handler(), this);
resultReceiver = new ExportService.ExportServiceResultReceiver(new Handler(), this);
if (savedInstanceState == null) {
autoConflict = ConflictResolutionStrategy.CONFLICT_NONE;
@@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.util.Log;
@@ -57,4 +58,37 @@ public class ExportService extends JobIntentService {
int resultCode = success ? ExportServiceResultReceiver.RESULT_CODE_SUCCESS : ExportServiceResultReceiver.RESULT_CODE_ERROR;
resultReceiver.send(resultCode, bundle);
}
public static class ExportServiceResultReceiver extends ResultReceiver {
public static final int RESULT_CODE_SUCCESS = 1;
public static final int RESULT_CODE_ERROR = 0;
public static final String RESULT_EXTRA_EXPORT_TASK = "result_extra_export_task";
private final Receiver receiver;
public ExportServiceResultReceiver(Handler handler, @NonNull Receiver receiver) {
super(handler);
this.receiver = receiver;
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
ExportTask exportTask = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_EXPORT_TASK);
switch (resultCode) {
case RESULT_CODE_SUCCESS -> receiver.onExportSuccess(exportTask);
case RESULT_CODE_ERROR -> receiver.onExportError(exportTask);
default -> throw new RuntimeException("Unknown resultCode.");
}
}
public interface Receiver {
default void onExportSuccess(ExportTask exportTask) {
}
default void onExportError(ExportTask exportTask) {
}
}
}
}
@@ -1,44 +0,0 @@
package de.dennisguse.opentracks.io.file.exporter;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import androidx.annotation.NonNull;
/**
* Create a new ResultReceive to receive results.
* Your {@link #onReceiveResult} method will be called from the thread running <var>handler</var> if given, or from an arbitrary thread if null.
*/
public class ExportServiceResultReceiver extends ResultReceiver {
public static final int RESULT_CODE_SUCCESS = 1;
public static final int RESULT_CODE_ERROR = 0;
public static final String RESULT_EXTRA_EXPORT_TASK = "result_extra_export_task";
private final Receiver receiver;
public ExportServiceResultReceiver(Handler handler, @NonNull Receiver receiver) {
super(handler);
this.receiver = receiver;
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
ExportTask exportTask = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_EXPORT_TASK);
switch (resultCode) {
case RESULT_CODE_SUCCESS -> receiver.onExportSuccess(exportTask);
case RESULT_CODE_ERROR -> receiver.onExportError(exportTask);
default -> throw new RuntimeException("Unknown resultCode.");
}
}
public interface Receiver {
default void onExportSuccess(ExportTask exportTask) {
}
default void onExportError(ExportTask exportTask) {
}
}
}
@@ -1,146 +1,67 @@
package de.dennisguse.opentracks.services;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.app.job.JobService;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.core.app.JobIntentService;
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;
public class TrackDeleteService extends Service {
public class TrackDeleteService extends JobIntentService {
private static final String CHANNEL_ID = TrackDeleteService.class.getSimpleName();
private static final int NOTIFICATION_ID = 1;
private static final int JOB_ID = 3;
static final String EXTRA_TRACK_IDS = "extra_track_ids";
private static final String EXTRA_RECEIVER = "extra_receiver";
private final Binder binder = new Binder();
private ExecutorService serviceExecutor;
private MutableLiveData<DeletionFinishedStatus> deleteResultObservable;
private NotificationManager notificationManager;
private NotificationCompat.Builder notificationBuilder;
private static final String EXTRA_TRACK_IDS = "extra_track_ids";
@Override
public void onCreate() {
serviceExecutor = Executors.newSingleThreadExecutor();
deleteResultObservable = new MutableLiveData<>();
public static void enqueue(Context context, TrackDeleteResultReceiver receiver, ArrayList<Track.Id> toBeDeleted) {
Intent intent = new Intent(context, JobService.class);
intent.putExtra(EXTRA_RECEIVER, receiver);
intent.putParcelableArrayListExtra(EXTRA_TRACK_IDS, toBeDeleted);
enqueueWork(context, TrackDeleteService.class, JOB_ID, intent);
}
@Override
public void onDestroy() {
super.onDestroy();
if (serviceExecutor != null) {
serviceExecutor.shutdownNow();
serviceExecutor = null;
}
deleteResultObservable = null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
protected void onHandleWork(@NonNull Intent intent) {
ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_RECEIVER);
ArrayList<Track.Id> trackIds = intent.getParcelableArrayListExtra(EXTRA_TRACK_IDS);
createAndShowNotification(trackIds.size());
deleteTracks(trackIds);
return START_NOT_STICKY;
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
contentProviderUtils.deleteTracks(this, trackIds);
resultReceiver.send(TrackDeleteResultReceiver.RESULT_CODE_SUCCESS, new Bundle());
}
private void deleteTracks(@NonNull List<Track.Id> trackIds) {
serviceExecutor.execute(() -> {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
contentProviderUtils.deleteTracks(this, trackIds);
sendResult(trackIds);
stopSelf();
});
}
public static class TrackDeleteResultReceiver extends ResultReceiver {
private void sendResult(List<Track.Id> trackIds) {
if (deleteResultObservable != null) {
deleteResultObservable.postValue(new DeletionFinishedStatus(trackIds));
}
}
public static final int RESULT_CODE_SUCCESS = 1;
@Override
public IBinder onBind(Intent intent) {
return binder;
}
private final Receiver receiver;
public LiveData<DeletionFinishedStatus> getDeletingStatusObservable() {
return deleteResultObservable;
}
/**
* Starts and shows the notification.
*
* @param tracksToDelete number of tracks to be deleted.
*/
private void createAndShowNotification(int tracksToDelete) {
notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, this.getString(R.string.app_name), NotificationManager.IMPORTANCE_LOW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
notificationChannel.setAllowBubbles(true);
}
notificationManager.createNotificationChannel(notificationChannel);
public TrackDeleteResultReceiver(Handler handler, @NonNull Receiver receiver) {
super(handler);
this.receiver = receiver;
}
notificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
notificationBuilder
.setContentTitle(this.getString(R.string.track_delete_progress_message))
.setContentText(this.getString(R.string.track_delete_number_of_tracks, tracksToDelete))
.setSmallIcon(R.drawable.ic_logo_color_24dp)
.setProgress(0, 0, true);
startForeground(NOTIFICATION_ID, notificationBuilder.build());
}
public class Binder extends android.os.Binder {
private Binder() {
super();
}
public TrackDeleteService getService() {
return TrackDeleteService.this;
}
}
public static class DeletionFinishedStatus {
private final List<Track.Id> trackIds;
/**
* @param trackIds List of deleted Track.Ids.
*/
private DeletionFinishedStatus(@Nullable List<Track.Id> trackIds) {
this.trackIds = trackIds;
}
public boolean isDeleted(Track.Id trackId) {
return this.trackIds != null && this.trackIds.contains(trackId);
}
@NonNull
@Override
public String toString() {
return "DeleteStatus{" +
"trackIds=" + trackIds +
'}';
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case RESULT_CODE_SUCCESS -> receiver.onDeleteFinished();
default -> throw new RuntimeException("Unknown resultCode.");
}
}
public interface Receiver {
void onDeleteFinished();
}
}
}
@@ -1,66 +0,0 @@
package de.dennisguse.opentracks.services;
import static de.dennisguse.opentracks.services.TrackDeleteService.EXTRA_TRACK_IDS;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.data.models.Track;
public class TrackDeleteServiceConnection implements ServiceConnection {
final private Listener listener;
private TrackDeleteService trackDeleteService;
public TrackDeleteServiceConnection(@NonNull Listener listener) {
this.listener = listener;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
trackDeleteService = ((TrackDeleteService.Binder) service).getService();
listener.connected(trackDeleteService);
}
@Override
public void onServiceDisconnected(ComponentName name) {
trackDeleteService = null;
}
public void startAndBind(Context context, ArrayList<Track.Id> trackIds) {
if (trackDeleteService != null) {
return;
}
Intent intent = new Intent(context, TrackDeleteService.class)
.putParcelableArrayListExtra(EXTRA_TRACK_IDS, trackIds);
context.startService(intent);
bind(context);
}
public void bind(Context context) {
if (trackDeleteService != null) {
return;
}
context.bindService(new Intent(context, TrackDeleteService.class), this, BuildConfig.DEBUG ? Context.BIND_DEBUG_UNBIND : 0);
}
public void unbind(Context context) {
context.unbindService(this);
trackDeleteService = null;
}
public interface Listener {
void connected(@NonNull TrackDeleteService service);
}
}
@@ -18,6 +18,8 @@ package de.dennisguse.opentracks.services;
import android.app.Service;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager.WakeLock;
@@ -205,7 +207,11 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
private void startSensors() {
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
trackPointCreator.start(this, handler);
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION + ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
} else {
startForeground(TrackRecordingServiceNotificationManager.NOTIFICATION_ID, notificationManager.setGPSonlyStarted(this));
}
}
public void endCurrentTrack() {
@@ -25,11 +25,15 @@ public class AltitudeCorrectionManager {
public AltitudeCorrectionManager() {
this.altitudeConverterFallback = new EGM2008Internal();
this.altitudeConverter = Build.VERSION.SDK_INT >= 34 ? new AltitudeConverter() : null;
this.altitudeConverter = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE ? new AltitudeConverter() : null;
}
public void correctAltitude(Context context, TrackPoint trackPoint) {
if (Build.VERSION.SDK_INT >= 34 && altitudeConverter != null) {
if (!trackPoint.hasLocation() || !trackPoint.hasAltitude()) {
Log.d(TAG, "No altitude correction necessary.");
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && altitudeConverter != null) {
try {
Location loc = trackPoint.getLocation();
altitudeConverter.addMslAltitudeToLocation(context, loc);
@@ -52,11 +56,6 @@ public class AltitudeCorrectionManager {
private EGM2008Utils.EGM2008Correction egm2008Correction;
public void correctAltitude(Context context, TrackPoint trackPoint) {
if (!trackPoint.hasLocation() || !trackPoint.hasAltitude()) {
Log.d(TAG, "No altitude correction necessary.");
return;
}
if (egm2008Correction == null || !egm2008Correction.canCorrect(trackPoint.getLocation())) {
try {
egm2008Correction = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
@@ -7,6 +7,7 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -40,8 +41,6 @@ public class AggregatedStatisticsActivity extends AbstractActivity implements Fi
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewBinding.aggregatedStatsList.setEmptyView(viewBinding.aggregatedStatsEmptyView);
areFiltersApplied = savedInstanceState != null && savedInstanceState.getBoolean(STATE_ARE_FILTERS_APPLIED);
List<Track.Id> trackIds = getIntent().getParcelableArrayListExtra(EXTRA_TRACK_IDS);
@@ -49,21 +48,35 @@ public class AggregatedStatisticsActivity extends AbstractActivity implements Fi
trackIds.stream().forEach(selection::addTrackId);
}
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
adapter = new AggregatedStatisticsAdapter(this, null);
viewBinding.aggregatedStatsList.setLayoutManager(layoutManager);
viewBinding.aggregatedStatsList.setAdapter(adapter);
viewModel = new ViewModelProvider(this).get(AggregatedStatisticsModel.class);
viewModel.getAggregatedStats(selection).observe(this, aggregatedStatistics -> {
if ((aggregatedStatistics == null || aggregatedStatistics.getCount() == 0) && !selection.isEmpty()) {
viewBinding.aggregatedStatsEmptyView.setText(getString(R.string.aggregated_stats_filter_no_results));
}
if (aggregatedStatistics != null) {
adapter = new AggregatedStatisticsAdapter(this, aggregatedStatistics);
viewBinding.aggregatedStatsList.setAdapter(adapter);
adapter.swapData(aggregatedStatistics);
}
adapter.notifyDataSetChanged();
checkListEmpty();
});
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
}
private void checkListEmpty() {
if (adapter.getItemCount() == 0) {
viewBinding.aggregatedStatsList.setVisibility(View.GONE);
viewBinding.aggregatedStatsEmptyView.setVisibility(View.VISIBLE);
} else {
viewBinding.aggregatedStatsList.setVisibility(View.VISIBLE);
viewBinding.aggregatedStatsEmptyView.setVisibility(View.GONE);
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
@@ -1,15 +1,16 @@
package de.dennisguse.opentracks.ui.aggregatedStatistics;
import android.content.Context;
import android.content.res.Resources;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
@@ -21,7 +22,7 @@ import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.util.StringUtils;
public class AggregatedStatisticsAdapter extends BaseAdapter {
public class AggregatedStatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private AggregatedStatistics aggregatedStatistics;
private final Context context;
@@ -31,54 +32,38 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
this.aggregatedStatistics = aggregatedStatistics;
}
@NonNull
@Override
public int getCount() {
if (aggregatedStatistics != null) {
return aggregatedStatistics.getCount();
}
return 0;
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.aggregated_stats_list_item, parent, false);
return new ViewHolder(view);
}
@Override
public AggregatedStatistics.AggregatedStatistic getItem(int position) {
return aggregatedStatistics.getItem(position);
}
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
ViewHolder viewHolder = (ViewHolder) holder;
@Override
public long getItemId(int position) {
// Not important because data are not database register but cooked data.
return position;
}
AggregatedStatistics.AggregatedStatistic aggregatedStatistic = aggregatedStatistics.getItem(position);
@Override
public View getView(int position, View convertView, ViewGroup parent) {
AggregatedStatistics.AggregatedStatistic aggregatedStatistic = getItem(position);
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.aggregated_stats_list_item, parent, false);
viewHolder = new ViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
Resources resources = context.getResources();
String localizedActivityType = aggregatedStatistic.getActivityTypeLocalized();
if (ActivityType.findByLocalizedString(resources, localizedActivityType)
.isShowSpeedPreferred()) {
String type = aggregatedStatistic.getActivityTypeLocalized();
if (ActivityType.findByLocalizedString(context, type).isShowSpeedPreferred()) {
viewHolder.setSpeed(aggregatedStatistic);
} else {
viewHolder.setPace(aggregatedStatistic);
}
return convertView;
}
public AggregatedStatistics swapData(AggregatedStatistics aggregatedStatistics) {
@Override
public int getItemCount() {
if (aggregatedStatistics == null) {
return 0;
}
return aggregatedStatistics.getCount();
}
public void swapData(AggregatedStatistics aggregatedStatistics) {
this.aggregatedStatistics = aggregatedStatistics;
this.notifyDataSetChanged();
return aggregatedStatistics;
}
public List<String> getCategories() {
@@ -89,7 +74,7 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
return categories;
}
private class ViewHolder {
private class ViewHolder extends RecyclerView.ViewHolder {
private final ImageView sportIcon;
private final TextView typeLabel;
private final TextView numTracks;
@@ -108,6 +93,7 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
private boolean reportSpeed;
public ViewHolder(View view) {
super(view);
sportIcon = view.findViewById(R.id.aggregated_stats_sport_icon);
typeLabel = view.findViewById(R.id.aggregated_stats_type_label);
numTracks = view.findViewById(R.id.aggregated_stats_num_tracks);
@@ -183,9 +169,7 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
private int getIcon(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
String localizedActivityType = aggregatedStatistic.getActivityTypeLocalized();
String iconValue = ActivityType.findByLocalizedString(context, localizedActivityType)
.getIconId();
return ActivityType.findBy(iconValue)
return ActivityType.findByLocalizedString(context, localizedActivityType)
.getIconDrawableId();
}
}
@@ -25,7 +25,7 @@ import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
import de.dennisguse.opentracks.io.file.exporter.ExportService;
import de.dennisguse.opentracks.io.file.exporter.ExportServiceResultReceiver;
import de.dennisguse.opentracks.io.file.exporter.ExportService.ExportServiceResultReceiver;
import de.dennisguse.opentracks.io.file.exporter.ExportTask;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
import de.dennisguse.opentracks.settings.PreferencesUtils;
+3 -2
View File
@@ -23,10 +23,11 @@
android:paddingBottom="?attr/actionBarSize"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<ListView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/aggregated_stats_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:visibility="gone" />
<TextView
android:id="@+id/aggregated_stats_empty_view"
@@ -3,11 +3,11 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:paddingBottom="24dp">
android:paddingBottom="8dp">
<!-- Guidelines -->
<androidx.constraintlayout.widget.Guideline
@@ -166,4 +166,12 @@
app:layout_constraintTop_toBottomOf="@+id/aggregated_stats_max_rate"
tools:text="@string/stats_max_speed" />
<View
android:id="@+id/aggregated_stats_horizontal_line"
style="@style/HorizontalLine"
android:layout_marginTop="8dp"
app:layout_constraintStart_toEndOf="@id/guideline1"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@id/aggregated_stats_max_rate_label" />
</androidx.constraintlayout.widget.ConstraintLayout>
+3
View File
@@ -627,4 +627,7 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="voiceSecondsPlural">{n, plural, =1 {1 segundo} other {{n,number} segundos} }</string>
<string name="voiceSpeedMilesPerHourPlural">{n, plural, =1 {1 milla por hora} other {{n,number,#.0} millas por hora} }</string>
<string name="voiceDistanceNauticalMilesPlural">{n, plural, =1 {1 milla náutica} other {{n,number,#.0} millas náuticas} }</string>
<string name="sensor_state_cadence_unit">rpm</string>
<string name="sensor_state_heart_rate_unit">lpm</string>
<string name="sensor_state_power_unit">W</string>
</resources>
+47 -9
View File
@@ -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">zrakoplov</string>
<string name="activity_type_atv">ATV</string>
@@ -152,7 +152,7 @@ limitations under the License.
<string name="menu_aggregated_statistics">Skupna statistika</string>
<string name="menu_delete">Izbriši</string>
<string name="menu_edit">Uredi</string>
<string name="menu_export_all">Izvezi sve</string>
<string name="menu_export_all">Izvezi sve (jedna datoteka za pojediečanu pjesmu)</string>
<string name="menu_help">Pomoć</string>
<string name="menu_import_all">Uvezi sve</string>
<string name="menu_insert_marker">Umetni oznaku</string>
@@ -170,7 +170,7 @@ limitations under the License.
<!-- Search -->
<!-- Sensor State -->
<string name="sensor_state_heart_rate">Brzina otkucaja srca</string>
<string name="sensor_state_heart_rate_value">%1$d otkuc./min.</string>
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
<string name="sensor_state_power">Napajanje</string>
<!-- Settings Advanced -->
<string name="settings_advanced">Napredno</string>
@@ -199,10 +199,11 @@ limitations under the License.
<string name="settings_stats_units_imperial">Imperijalne (milje, stope)</string>
<string name="settings_stats_units_metric">Metričke (km, m)</string>
<string name="settings_stats_units_title">Preferirane jedinice</string>
<!-- Share Track -->
<string name="share_track_share_file_body">Mislim da bi vam ova staza mogla biti zanimljiva.\n\nDa biste ju pogledali, upotrijebite Google Earth ili neki drugi softver za izradu karata.\n\n%1$s</string>
<string name="share_track_subject">Želim podijeliti stazu s vama.</string>
<string name="share_track_share_file_body">Mislim da bi te ova pjesma mogla zanimati.
\n
\n%1$s</string>
<string name="share_track_subject">Želim podijeliti pjesmu s tobom</string>
<!-- Stats -->
<string name="stats_average_moving_pace">Prosj. tempo kretanja</string>
<string name="stats_average_moving_speed">Prosj. brzina kretanja</string>
@@ -336,4 +337,41 @@ limitations under the License.
<string name="waypoint_type_waterfall">vodopad</string>
<string name="waypoint_type_water_fountain">fontana</string>
<string name="waypoint_type_waypoint">točka rute</string>
</resources>
<string name="settings_import_section">Uvoz</string>
<string name="settings_export_section">Izvoz</string>
<string name="settings_prevent_reimport_tracks_title">Spriječi ponovni uvoz pjesama</string>
<string name="menu_export_all_one_file">Izvezi sve (jedna datoteka za sve pjesme)</string>
<string name="menu_select_layout">Odaberi raspored</string>
<string name="sensor_state_heart_rate_max">Maksimalna brzina otkucaja srca</string>
<string name="menu_voice_rate">Brzina glasa</string>
<string name="sensor_state_heart_rate_avg">Prosječna brzina otkucaja srca</string>
<string name="permission_gps_failed">OpenTracks zahtijeva dozvolu za korištenje GPS-a.</string>
<string name="sensor_state_cadence_avg">Prosječna kadenca</string>
<string name="permission_bluetooth_failed">OpenTracks zahtijeva dozvolu za korištenje Bluetootha.</string>
<string name="permission_bluetooth_failed_rejected">OpenTracksu moraš ručno dozvoliti pristup obližnjim uređajima u postavkama sustava.</string>
<string name="menu_add_custom_layout">Novi raspored</string>
<string name="settings_sensors_title">Bluetooth senzori</string>
<string name="menu_filter">Filtar</string>
<string name="delete_service">Usluga brisanja</string>
<string name="settings_recording_keep_screen_on_while_recording_summary">Tijekom snimanja ostavi ekran uključenim.</string>
<string name="settings_import_export_summary">Uvoz, Izvoz, Automatski izvoz</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Cjeloekranski prikaz</string>
<string name="settings_import_export_title">Uvoz/Izvoz</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Tijekom snimanja prikaži cjeloekranski prikaz.</string>
<string name="sensor_state_cadence_max">Maksimalna kadenca</string>
<string name="settings_units_category">Jedinice</string>
<string name="settings_open_tracks_summary">Sve o projektu OpenTracks</string>
<string name="settings_gps_title">GPS</string>
<string name="settings_announcements_summary">Vrijeme, udaljenost, brzina glasa</string>
<string name="menu_resume_track">Nastavi svirati pjesmu</string>
<string name="settings_ui_summary">Izgled, raspored snimanja, tema</string>
<string name="settings_defaults_title">Standardne jedinice i aktivnost</string>
<string name="settings_ui_title">Korisničko sučelje</string>
<string name="settings_export_filename_title">Format imena datoteke</string>
<string name="settings_recording_show_on_lockscreen_while_recording_summary">Tijekom snimanja prikaži statistiku bez otključavanja uređaja.</string>
<string name="menu_insert_gallery_img">Umetni sliku galerije</string>
<string name="sensor_state_heart_rate_unit">bpm</string>
<string name="bluetooth_disabled">Aktiviraj Bluetooth.</string>
<string name="sensor_state_cadence_unit">rpm</string>
<string name="settings_recording_keep_screen_on_while_recording_title">Ostavi ekran uključenim</string>
</resources>
+49 -45
View File
@@ -16,15 +16,15 @@ limitations under the License.
--><resources>
<!-- Activity Type -->
<string name="activity_type_airplane">літак</string>
<string name="activity_type_atv">Мотовсюдиходи</string>
<string name="activity_type_biking">велосипедний спорт</string>
<string name="activity_type_atv">мотовсюдиходи</string>
<string name="activity_type_biking">велоспорт</string>
<string name="activity_type_blimp">дирижабль</string>
<string name="activity_type_boat">човен</string>
<string name="activity_type_climbing">скелелазіння</string>
<string name="activity_type_commercial_airplane">комерційний літак</string>
<string name="activity_type_cross_country_skiing">лижні перегони</string>
<string name="activity_type_cross_country_skiing">біг на лижах</string>
<string name="activity_type_cycling">велоспорт</string>
<string name="activity_type_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>
@@ -82,14 +82,14 @@ limitations under the License.
<string name="activity_type_water_skiing">водні лижі</string>
<string name="activity_type_wind_surfing">віндсерфінг</string>
<!-- Description -->
<string name="description_activity_type">Тип тренування: %1$s</string>
<string name="description_average_moving_pace_in_minute">Середній темп: %1$s хв/км (%2$s хв/мл)</string>
<string name="description_average_moving_speed">Середня швидкість: %1$.2f км/год (%2$.1f мл/год)</string>
<string name="description_activity_type">Тип активності: %1$s</string>
<string name="description_average_moving_pace_in_minute">Середній темп руху: %1$s хв/км (%2$s хв/мл)</string>
<string name="description_average_moving_speed">Середня швидкість руху: %1$.2f км/год (%2$.1f мл/год)</string>
<string name="description_average_pace_in_minute">Середній темп: %1$s хв/км (%2$s хв/мл)</string>
<string name="description_average_speed">Середня швидкість: %1$.2f км/год (%2$.1f мл/год)</string>
<string name="description_altitude_gain">Сумарний підйом: %1$d м (%2$d фт)</string>
<string name="description_altitude_imperial">Підйом (фути)</string>
<string name="description_altitude_metric">Підйом (м)</string>
<string name="description_altitude_gain">Збільшення висоти: %1$d м (%2$d фт)</string>
<string name="description_altitude_imperial">Висота (фути)</string>
<string name="description_altitude_metric">Висота (метри)</string>
<string name="description_fastest_pace_in_minute">Найшвидший темп: %1$s хв/км (%2$s хв/мл)</string>
<string name="description_max_altitude">Максимальний підйом: %1$d м (%2$d фт)</string>
<string name="description_max_speed">Макс. швидкість: %1$.2f км/год (%2$.1f мл/год)</string>
@@ -355,34 +355,34 @@ limitations under the License.
<string name="instant_export_enabled_summary">Експорт треку в сховище після завершення запису</string>
<string name="instant_export_enabled_title">Миттєвий експорт після тренування</string>
<string name="help_gps_troubleshooting_content"> Що ви можете зробити:
\na) перевірте, чи увімкнено GPS,
\nb) покращити прийом GPS (наприклад, вийти на вулицю),
\nc) перевірити за допомогою застосунку <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> та
\nd) вимкнути енергозбереження.
\n a) перевірте, чи увімкнено GPS,
\n b) покращте прийом GPS (наприклад, вийдіть на вулицю),
\n c) перевірте за допомогою застосунку <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> та
\n d) вимкніть енергоощадження.
\n
\nЯкщо це не допомогло - будь ласка, повідомте про помилку. </string>
<string name="about_preference_title">Про Open Tracks</string>
<string name="about_url">Сторінка проекта: <a href="%1$s">%1$s</a></string>
\n Якщо нічого не допомагає, будь ласка, повідомте нам про помилку. </string>
<string name="about_preference_title">Про OpenTracks</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_libraries">Бібліотеки</string>
<string name="help_feature_title">OpenTracks має такі особливості:</string>
<string name="help_storage_pictures_title">Як OpenTracks зберігає зображення зроблені за допомогою маркувань\?</string>
<string name="help_storage_pictures_content">Вони зберігаються у внутрішній пам\'яті застосунку (тобто, доступні тільки для OpenTracks).</string>
<string name="help_data_google_play_services_title">Чи потребує OpenTracks використання Google Play Service\?</string>
<string name="help_data_google_play_services_title">Чи потрібен/використовує OpenTrack Google Play Service\?</string>
<string name="help_track_map_title">Як показати трек на мапі\?</string>
<string name="help_data_storage_title">Як OpenTracks зберігає дані датчиків\?</string>
<string name="help_data_storage_title">Як OpenTracks зберігає дані з датчиків\?</string>
<string name="help_recording_title">Як OpenTracks записує дані\?</string>
<string name="help_track_export_title">Як я можу експортувати або обмінятися записаним треком\?</string>
<string name="help_track_export_title">Як я можу експортувати/поширювати записані треки\?</string>
<string name="settings_night_mode_title">Тема UI</string>
<string name="help_gps_troubleshooting_title">OpenTracks не записує моє місцеположення\?</string>
<string name="help_support_title">Як я можу підтримати OpenTracks\?</string>
<string name="open_track_as_KML">Відкрити як KML</string>
<string name="open_track_as_GPX">Відкрити як GPX</string>
<string name="open_track_as_trackfileformat">Відкрити яка %1$s</string>
<string name="open_track_as_trackfileformat">Відкрити як %1$s</string>
<string name="activity_type_escooter">електро-байк</string>
<string name="settings_sensor_wheel_circumference">Окружність колеса (мм)</string>
<string name="description_altitude_loss">Втрата висоти: %1$d м (%2$d фут.)</string>
<string name="description_altitude_loss">Зменшення висоти: %1$d м (%2$d фут.)</string>
<string name="export_progress_summary_new_msg">Нові треки експортовано</string>
<string name="export_progress_summary_overwrite_msg">Треки перезаписано</string>
<string name="export_progress_summary_skip_msg">Треки пропущено</string>
@@ -416,25 +416,25 @@ limitations under the License.
<string name="menu_resume_track">Відновити трек</string>
<string name="permission_gps_failed">OpenTracks вимагає дозволу на використання GPS.</string>
<string name="settings_night_mode_option_system">Системна</string>
<string name="about_description"><i>Open Tracks</i> це спортивний GPS трекер який поважає вашу приватність.</string>
<string name="about_history">Open Tracks це відгалуження застосунку <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (закритого в 2016р).</string>
<string name="help_feature_content">* запис GPS-шляху
\n* використання датчика ритму серця (тільки Bluetooth LE)
\n* голосовий путівник (за часом або дистацією)
\n* створення маркувань (включаючи фотознімки)
<string name="about_description"><i>OpenTracks</i> - це застосунок для відстеження спортивної діяльності, який поважає вашу приватність.</string>
<string name="about_history">OpenTracks це відгалуження застосунку <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (закритого в 2016р).</string>
<string name="help_feature_content">* створення GPS треків
\n* використання датчика серцевого ритму (тільки Bluetooth LE)
\n* голосовий путівник (за часом або відстанню)
\n* створення маркувань (включаючи зображення)
\n* повноцінний автономний режим
\n* відображення щляхів на мапі (див. нижче)</string>
<string name="help_track_export_content"> Треки можна експортувати з налаштувань як KMZ, KML або GPX, KMZ/KML всі дані або тільки розташування GPX. Доріжки, опубліковані за допомогою <i>значка поширення</i>, експортуються як KMZ. </string>
<string name="help_recording_content">OpenTracks використовує датчик GPS пристрою для безперервного запису місця розташування. Якщо пристрій GPS повідомляє неточні дані (наприклад, місце розташування, швидкість, висоту), OpenTracks нічого не може з цим вдіяти.</string>
<string name="help_data_storage_content"> Дані датчика зиписуються в кожному зареєстрованому місці. Що стосується датчика частоти пульсу, це означає, що для кожного місця розташування зберігається тільки <i>одне</i> вимірювання частоти пульсу. Якщо ви зупинилися або перебуваєте в приміщенні, дані датчика не будуть записані (але будуть відображатися). </string>
\n* відображення треків на мапі (див. нижче)</string>
<string name="help_track_export_content"> Треки можна експортувати з налаштувань у форматах KMZ, KML або GPX. KMZ/KML всі дані, а GPX тільки розташування. Треки, опубліковані за допомогою <i>значка поширення</i>, експортуються як KMZ. </string>
<string name="help_recording_content">OpenTracks використовує GPS датчик пристрою для безперервного запису місцезнаходження. Якщо GPS пристрій повідомляє неточні дані (наприклад, місцеперебування, швидкість, висота), OpenTracks нічого не може з цим вдіяти.</string>
<string name="help_data_storage_content"> Дані датчика зиписуються в кожному зареєстрованому місці. Що стосується датчика частоти серцевих скорочень для кожного місцезнаходження зберігається тільки <i>одне</i> вимірювання частоти. Якщо ви зупинилися або перебуваєте в приміщенні, дані датчика не будуть записані (але будуть відображатися). </string>
<string name="help_support_content"> Ви можете:
\nа) поширювати інформацію про OpenTracks,
\nb) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">пропонувати функції</a>,
\nc) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">повідомляти про помилки</a>,
\nd) допомогти в розробці програмного забезпечення,
\ne) купувати в <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
\nf) зробити фінансовий внесок, та
\ng) робити все, що, на вашу думку, допомагає OpenTracks. </string>
\n а) поширювати інформацію про OpenTracks,
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">пропонувати нові функції</a>,
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">повідомляти про помилки</a>,
\n d) допомогти в розробці програмного забезпечення,
\n e) купувати в <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
\n f) зробити фінансовий внесок, та
\n g) робити все, що, на вашу думку, допомагає OpenTracks. </string>
<string name="description_default_speed_or_pace">За активністю</string>
<string name="export_error_post_workout">Не вдалося виконати експорт після тренування, перевірте каталог експорту.</string>
<string name="aggregated_stats_empty_message">Запишіть свій перший трек, щоб побачити загальну статистику</string>
@@ -449,10 +449,10 @@ limitations under the License.
<string name="stats_loss">Втрачено</string>
<string name="share_image_subject">Я хотів би поділитися з вами зображенням</string>
<string name="settings_default_export_uri_title">Каталог експорту треків</string>
<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 є окремим застосунком, який може отримати тимчасовий доступ до даних треку які будуть відображені на карті.
\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_track_map_content"> Використовуйте маленьку піктограму мапи.
\n a) (оновлення в реальному часі) Встановіть застосунок <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Він дозволяє дивитися карти онлайн чи офлайн завдяки <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
\n Насправді OSMDashboard є окремим застосунком, який має тимчасовий доступ лише до даних треку(ів) які будуть відображені.
\n b) Через звичайний картографічний застосунок (на приклад, <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="settings_prevent_reimport_tracks_title">Заборонити повторний імпорт треків</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">Завершено з %1$d однією помилкою</item>
@@ -460,9 +460,9 @@ limitations under the License.
<item quantity="many">Завершено з %1$d помилками</item>
<item quantity="other">Завершено з %1$d помилками</item>
</plurals>
<string name="settings_recording_show_on_lockscreen_while_recording_title">Показувати статистику на заблокованому єкрані</string>
<string name="settings_recording_show_on_lockscreen_while_recording_title">Показувати статистику на заблокованому екрані</string>
<string name="settings_recording_keep_screen_on_while_recording_summary">Під час запису тримати екран включеним.</string>
<string name="settings_recording_keep_screen_on_while_recording_title">Єкран завжди включено</string>
<string name="settings_recording_keep_screen_on_while_recording_title">Екран завжди включено</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Повний екран</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Під час запису бути в повноекранному режимі.</string>
<string name="hold_to_stop">Затисніть щоб зупинити</string>
@@ -545,4 +545,8 @@ limitations under the License.
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
<string name="settings_gps_title">GPS</string>
<string name="no_compatible_file_manager_installed">Не встановлено сумісний файловий менеджер</string>
<string name="help_data_google_play_services_content">Ні</string>
<string name="activity_type_workout">тренування</string>
<string name="activity_type_swimming_open">плавання у відкритих водоймах</string>
<string name="activity_type_swimming">плавання</string>
</resources>