forked from upstream-mirrors/OpenTracks
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c4235b1961 | |||
| 2b73139051 | |||
| 1a2d263945 | |||
| e69bea802f | |||
| 4baaff8374 | |||
| 1ed0a11c2f | |||
| 971b0196da | |||
| 45f41d6401 | |||
| 42234515cb | |||
| 10e479ceb9 | |||
| 88d8a808f3 | |||
| f15ae48846 | |||
| 5bf4cef57d |
+2
-2
@@ -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
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-44
@@ -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() {
|
||||
|
||||
+6
-7
@@ -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());
|
||||
|
||||
+18
-5
@@ -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);
|
||||
|
||||
+25
-41
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user