mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-02 09:33:06 +02:00
@@ -349,6 +349,7 @@ limitations under the License.
|
||||
<activity android:name=".ui.aggregatedStatistics.AggregatedStatisticsActivity" />
|
||||
|
||||
<activity android:name=".io.file.importer.DirectoryChooserActivity$ExportDirectoryChooserActivity" />
|
||||
<activity android:name=".io.file.importer.DirectoryChooserActivity$ExportDirectoryChooserOneFileActivity" />
|
||||
<activity android:name=".io.file.importer.DirectoryChooserActivity$DefaultTrackExportDirectoryChooserActivity" />
|
||||
|
||||
<activity android:name=".io.file.exporter.ExportActivity" />
|
||||
|
||||
@@ -213,7 +213,7 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
PipeDataWriter<String> pipeDataWriter = (output, uri1, mimeType, opts, args) -> {
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
|
||||
// TODO handle failure (i.e., do not export an empty file)
|
||||
trackExporter.writeTrack(tracks.toArray(new Track[0]), fileOutputStream);
|
||||
trackExporter.writeTrack(tracks, fileOutputStream);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "there occurred an error while sharing a file: " + e);
|
||||
}
|
||||
|
||||
@@ -88,14 +88,27 @@ public class CSVTrackExporter implements TrackExporter {
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
|
||||
return writeTrack(new Track[]{track}, outputStream);
|
||||
return writeTrack(List.of(track), outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
|
||||
List<Column> columns = Collections.unmodifiableList(Arrays.asList(new Column("time", null), new Column("trackpoint_type", t -> quote(t.getType().name())), new Column("latitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLatitude()) : ""), new Column("longitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLongitude()) : ""), new Column("altitude", t -> t.hasAltitude() ? COORDINATE_FORMAT.format(t.getAltitude().toM()) : ""), new Column("accuracy_horizontal", t -> t.hasHorizontalAccuracy() ? DISTANCE_FORMAT.format(t.getHorizontalAccuracy().toM()) : ""), new Column("accuracy_vertical", t -> t.hasVerticalAccuracy() ? DISTANCE_FORMAT.format(t.getVerticalAccuracy().toM()) : ""),
|
||||
public boolean writeTrack(@NonNull List<Track> tracks, @NonNull OutputStream outputStream) {
|
||||
List<Column> columns = List.of(
|
||||
new Column("time", null),
|
||||
new Column("trackpoint_type", t -> quote(t.getType().name())),
|
||||
new Column("latitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLatitude()) : ""),
|
||||
new Column("longitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLongitude()) : ""),
|
||||
new Column("altitude", t -> t.hasAltitude() ? COORDINATE_FORMAT.format(t.getAltitude().toM()) : ""),
|
||||
new Column("accuracy_horizontal", t -> t.hasHorizontalAccuracy() ? DISTANCE_FORMAT.format(t.getHorizontalAccuracy().toM()) : ""),
|
||||
new Column("accuracy_vertical", t -> t.hasVerticalAccuracy() ? DISTANCE_FORMAT.format(t.getVerticalAccuracy().toM()) : ""),
|
||||
|
||||
new Column("speed", t -> t.hasSpeed() ? SPEED_FORMAT.format(t.getSpeed().toKMH()) : ""), new Column("altitude_gain", t -> t.hasAltitudeGain() ? DISTANCE_FORMAT.format(t.getAltitudeGain()) : ""), new Column("altitude_loss", t -> t.hasAltitudeLoss() ? DISTANCE_FORMAT.format(t.getAltitudeLoss()) : ""), new Column("sensor_distance", t -> t.hasSensorDistance() ? DISTANCE_FORMAT.format(t.getSensorDistance().toM()) : ""), new Column("heartrate", t -> t.hasHeartRate() ? HEARTRATE_FORMAT.format(t.getHeartRate().getBPM()) : ""), new Column("cadence", t -> t.hasCadence() ? CADENCE_FORMAT.format(t.getCadence().getRPM()) : ""), new Column("power", t -> t.hasPower() ? ALTITUDE_FORMAT.format(t.getPower().getW()) : "")));
|
||||
new Column("speed", t -> t.hasSpeed() ? SPEED_FORMAT.format(t.getSpeed().toKMH()) : ""),
|
||||
new Column("altitude_gain", t -> t.hasAltitudeGain() ? DISTANCE_FORMAT.format(t.getAltitudeGain()) : ""),
|
||||
new Column("altitude_loss", t -> t.hasAltitudeLoss() ? DISTANCE_FORMAT.format(t.getAltitudeLoss()) : ""),
|
||||
new Column("sensor_distance", t -> t.hasSensorDistance() ? DISTANCE_FORMAT.format(t.getSensorDistance().toM()) : ""),
|
||||
new Column("heartrate", t -> t.hasHeartRate() ? HEARTRATE_FORMAT.format(t.getHeartRate().getBPM()) : ""),
|
||||
new Column("cadence", t -> t.hasCadence() ? CADENCE_FORMAT.format(t.getCadence().getRPM()) : ""),
|
||||
new Column("power", t -> t.hasPower() ? ALTITUDE_FORMAT.format(t.getPower().getW()) : ""));
|
||||
|
||||
try {
|
||||
prepare(outputStream);
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@@ -24,6 +23,7 @@ import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
@@ -31,11 +31,11 @@ import androidx.fragment.app.FragmentActivity;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||
import de.dennisguse.opentracks.databinding.ExportActivityBinding;
|
||||
import de.dennisguse.opentracks.io.file.ErrorListDialog;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
@@ -47,12 +47,20 @@ import de.dennisguse.opentracks.util.FileUtils;
|
||||
* An activity for saving tracks to the external storage.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
* TODO: This class needs some refactoring.
|
||||
* * It pushes each export job (usually one Track) for export; although export could be run in parallel.
|
||||
* Also this requires that the ExportActivity stays in foreground, so the user has to activily wait.
|
||||
* It would be better to let the ExportService handle this and let it report progress / conflicts to ExportActivity
|
||||
* * File name conflicts are checked in this class instead of the ExportService.
|
||||
* 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 {
|
||||
|
||||
private static final String TAG = ExportActivity.class.getSimpleName();
|
||||
|
||||
public static final String EXTRA_DIRECTORY_URI_KEY = "directory_uri";
|
||||
public static final String EXTRA_ONE_FILE_KEY = "one_file";
|
||||
public static final String EXTRA_TRACKFILEFORMAT_KEY = "trackfileformat";
|
||||
|
||||
private static final String BUNDLE_AUTO_CONFLICT = "auto_conflict";
|
||||
@@ -64,6 +72,8 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
private static final String BUNDLE_DIRECTORY_FILES = "track_directory_files";
|
||||
private static final String BUNDLE_TRACK_ERRORS = "track_errors";
|
||||
|
||||
private static final String BUNDLE_EXPORT_TASKS = "export_tasks";
|
||||
|
||||
private static final int CONFLICT_NONE = 0;
|
||||
private static final int CONFLICT_OVERWRITE = 1;
|
||||
private static final int CONFLICT_SKIP = 2;
|
||||
@@ -87,12 +97,13 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
|
||||
private ArrayList<String> trackErrors = new ArrayList<>();
|
||||
|
||||
// TODO Make this an enum
|
||||
private int autoConflict;
|
||||
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
// List of tracks to be exported.
|
||||
private final ArrayList<Track> tracks = new ArrayList<>();
|
||||
private ArrayList<ExportTask> exportTasks;
|
||||
|
||||
private final LinkedBlockingQueue<PendingConflict> conflictsQueue = new LinkedBlockingQueue<>();
|
||||
private final Handler conflictsHandler = new Handler();
|
||||
@@ -100,34 +111,36 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
private final Runnable conflictsRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (conflictsQueue.size() > 0) {
|
||||
PendingConflict conflict = conflictsQueue.peek();
|
||||
if (conflict.resolve()) {
|
||||
conflictsQueue.remove(conflict);
|
||||
if (!conflictsQueue.isEmpty()) {
|
||||
conflictsHandler.post(conflictsRunnable);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
viewBinding.exportProgressLeftButton.setOnClickListener((view) -> {
|
||||
setConflictVisibility(View.GONE);
|
||||
conflict.skip();
|
||||
conflictsQueue.remove(conflict);
|
||||
if (!conflictsQueue.isEmpty()) {
|
||||
conflictsHandler.post(conflictsRunnable);
|
||||
}
|
||||
});
|
||||
|
||||
viewBinding.exportProgressRightButton.setOnClickListener((view) -> {
|
||||
setConflictVisibility(View.GONE);
|
||||
conflict.overwrite();
|
||||
conflictsQueue.remove(conflict);
|
||||
if (!conflictsQueue.isEmpty()) {
|
||||
conflictsHandler.post(conflictsRunnable);
|
||||
}
|
||||
});
|
||||
if (conflictsQueue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PendingConflict conflict = conflictsQueue.peek();
|
||||
if (conflict.resolve()) {
|
||||
conflictsQueue.remove(conflict);
|
||||
if (!conflictsQueue.isEmpty()) {
|
||||
conflictsHandler.post(conflictsRunnable);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
viewBinding.exportProgressLeftButton.setOnClickListener((view) -> {
|
||||
setConflictVisibility(View.GONE);
|
||||
conflict.skip();
|
||||
conflictsQueue.remove(conflict);
|
||||
if (!conflictsQueue.isEmpty()) {
|
||||
conflictsHandler.post(conflictsRunnable);
|
||||
}
|
||||
});
|
||||
|
||||
viewBinding.exportProgressRightButton.setOnClickListener((view) -> {
|
||||
setConflictVisibility(View.GONE);
|
||||
conflict.overwrite();
|
||||
conflictsQueue.remove(conflict);
|
||||
if (!conflictsQueue.isEmpty()) {
|
||||
conflictsHandler.post(conflictsRunnable);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,6 +152,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
|
||||
directoryUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY);
|
||||
trackFileFormat = (TrackFileFormat) getIntent().getSerializableExtra(EXTRA_TRACKFILEFORMAT_KEY);
|
||||
boolean allInOneFile = getIntent().getBooleanExtra(EXTRA_ONE_FILE_KEY, false);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
|
||||
@@ -152,7 +166,10 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
setProgress();
|
||||
new Thread(() -> {
|
||||
directoryFiles = ExportUtils.getAllFiles(ExportActivity.this, documentFile.getUri());
|
||||
runOnUiThread(() -> initExport(0));
|
||||
runOnUiThread(() -> {
|
||||
createExportTasks(allInOneFile);
|
||||
nextExport(null);
|
||||
});
|
||||
}).start();
|
||||
} else {
|
||||
autoConflict = savedInstanceState.getInt(BUNDLE_AUTO_CONFLICT);
|
||||
@@ -163,9 +180,9 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
trackExportTotalCount = savedInstanceState.getInt(BUNDLE_TOTAL_COUNT);
|
||||
directoryFiles = savedInstanceState.getStringArrayList(BUNDLE_DIRECTORY_FILES);
|
||||
trackErrors = savedInstanceState.getStringArrayList(BUNDLE_TRACK_ERRORS);
|
||||
|
||||
exportTasks = new ArrayList<>(savedInstanceState.getParcelableArrayList(BUNDLE_EXPORT_TASKS));
|
||||
setProgress();
|
||||
initExport(getTotalDone());
|
||||
nextExport(null);
|
||||
}
|
||||
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(getString(R.string.export_progress_message, directoryDisplayName));
|
||||
@@ -183,13 +200,14 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
outState.putInt(BUNDLE_TOTAL_COUNT, trackExportTotalCount);
|
||||
outState.putStringArrayList(BUNDLE_DIRECTORY_FILES, (ArrayList<String>) directoryFiles);
|
||||
outState.putStringArrayList(BUNDLE_TRACK_ERRORS, trackErrors);
|
||||
outState.putParcelableArrayList(BUNDLE_EXPORT_TASKS, exportTasks);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
conflictsQueue.clear();
|
||||
tracks.clear();
|
||||
exportTasks.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -202,57 +220,50 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
this.doubleBackToCancel = true;
|
||||
Toast.makeText(this, getString(R.string.generic_click_twice_cancel), Toast.LENGTH_SHORT).show();
|
||||
|
||||
new Handler().postDelayed(() -> doubleBackToCancel=false, 2000);
|
||||
new Handler().postDelayed(() -> doubleBackToCancel = false, 2000);
|
||||
}
|
||||
|
||||
private void initExport(int from) {
|
||||
try (Cursor cursor = contentProviderUtils.getTrackCursor(null, null, TracksColumns._ID)) {
|
||||
if (cursor == null) {
|
||||
onExportEnded();
|
||||
return;
|
||||
}
|
||||
|
||||
trackExportTotalCount = cursor.getCount();
|
||||
viewBinding.exportProgressTotal.setText("" + trackExportTotalCount);
|
||||
for (int i = from; i < trackExportTotalCount; i++) {
|
||||
cursor.moveToPosition(i);
|
||||
Track track = ContentProviderUtils.createTrack(cursor);
|
||||
tracks.add(track);
|
||||
}
|
||||
|
||||
if (!tracks.isEmpty()) {
|
||||
export(tracks.get(0));
|
||||
} else {
|
||||
onExportEnded();
|
||||
}
|
||||
private void createExportTasks(boolean allInOneFile) {
|
||||
List<Track> tracks = contentProviderUtils.getTracks();
|
||||
exportTasks = new ArrayList<>();
|
||||
if (allInOneFile) {
|
||||
String filename = "OpenTracks-Backup";
|
||||
exportTasks.add(new ExportTask(filename, trackFileFormat, tracks.stream().map(Track::getId).collect(Collectors.toList())));
|
||||
} else {
|
||||
exportTasks.addAll(tracks.stream().map(it -> new ExportTask(null, trackFileFormat, List.of(it.getId()))).collect(Collectors.toList()));
|
||||
}
|
||||
trackExportTotalCount = exportTasks.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue track identified by UUID to be exported if not exported already or there is a conflict resolution.
|
||||
*
|
||||
* @param track Track object.
|
||||
* @param conflictResolution conflict resolution to be applied if needed.
|
||||
*/
|
||||
private void export(Track track, int conflictResolution) {
|
||||
boolean fileExists = exportFileExists(track);
|
||||
private void export(ExportTask exportTask, int conflictResolution) {
|
||||
boolean fileExists = exportFileExists(exportTask);
|
||||
|
||||
if (fileExists && conflictResolution == CONFLICT_NONE) {
|
||||
conflict(track);
|
||||
conflict(exportTask);
|
||||
} else if (fileExists && conflictResolution == CONFLICT_SKIP) {
|
||||
trackExportSkippedCount++;
|
||||
onExportCompleted(track);
|
||||
nextExport(exportTask);
|
||||
} else {
|
||||
ExportService.enqueue(this, resultReceiver, track.getId(), trackFileFormat, directoryUri);
|
||||
ExportService.enqueue(this, resultReceiver, exportTask, directoryUri);
|
||||
}
|
||||
}
|
||||
|
||||
private void export(Track track) {
|
||||
export(track, autoConflict);
|
||||
private void export(ExportTask exportTask) {
|
||||
export(exportTask, autoConflict);
|
||||
}
|
||||
|
||||
private boolean exportFileExists(Track track) {
|
||||
String filename = PreferencesUtils.getTrackFileformatGenerator().format(track, trackFileFormat);
|
||||
@Deprecated //TODO Check should be done in ExportService
|
||||
private boolean exportFileExists(ExportTask exportTask) {
|
||||
String filename;
|
||||
if (exportTask.isMultiExport()) {
|
||||
filename = exportTask.getFilename();
|
||||
} else {
|
||||
Track track = contentProviderUtils.getTrack(exportTask.getTrackIds().get(0));
|
||||
filename = PreferencesUtils.getTrackFileformatGenerator().format(track, trackFileFormat);
|
||||
}
|
||||
return directoryFiles.stream().anyMatch(filename::equals);
|
||||
}
|
||||
|
||||
@@ -269,31 +280,29 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
}
|
||||
|
||||
private void setProgress() {
|
||||
int done = getTotalDone();
|
||||
|
||||
viewBinding.exportProgressDone.setText("" + done);
|
||||
viewBinding.exportProgressDone.setText("" + getTotalDone());
|
||||
viewBinding.exportProgressTotal.setText("" + trackExportTotalCount);
|
||||
|
||||
viewBinding.exportProgressBar.setProgress((int) ((float) done / (float) trackExportTotalCount * 100f));
|
||||
viewBinding.exportProgressSummaryNew.setText(String.valueOf(trackExportSuccessCount));
|
||||
viewBinding.exportProgressSummaryOverwrite.setText(String.valueOf(trackExportOverwrittenCount));
|
||||
viewBinding.exportProgressSummarySkip.setText(String.valueOf(trackExportSkippedCount));
|
||||
viewBinding.exportProgressSummaryErrors.setText(String.valueOf(trackExportErrorCount));
|
||||
viewBinding.exportProgressBar.setProgress((int) ((float) getTotalDone() / (float) trackExportTotalCount * 100f));
|
||||
viewBinding.exportProgressSummaryNew.setText("" + trackExportSuccessCount);
|
||||
viewBinding.exportProgressSummaryOverwrite.setText("" + trackExportOverwrittenCount);
|
||||
viewBinding.exportProgressSummarySkip.setText("" + trackExportSkippedCount);
|
||||
viewBinding.exportProgressSummaryErrors.setText("" + trackExportErrorCount);
|
||||
viewBinding.exportProgressSummaryNewGroup.setVisibility(trackExportSuccessCount > 0 ? View.VISIBLE : View.GONE);
|
||||
viewBinding.exportProgressSummaryOverwriteGroup.setVisibility(trackExportOverwrittenCount > 0 ? View.VISIBLE : View.GONE);
|
||||
viewBinding.exportProgressSummarySkipGroup.setVisibility(trackExportSkippedCount > 0 ? View.VISIBLE : View.GONE);
|
||||
viewBinding.exportProgressSummaryErrorsGroup.setVisibility(trackExportErrorCount > 0 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void onExportCompleted(Track track) {
|
||||
tracks.remove(track);
|
||||
private void nextExport(@Nullable ExportTask exportTask) {
|
||||
exportTasks.remove(exportTask);
|
||||
|
||||
setProgress();
|
||||
if (tracks.isEmpty()) {
|
||||
if (exportTasks.isEmpty()) {
|
||||
onExportEnded();
|
||||
return;
|
||||
}
|
||||
export(tracks.get(0));
|
||||
export(exportTasks.get(0));
|
||||
}
|
||||
|
||||
private void onExportEnded() {
|
||||
@@ -318,30 +327,32 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExportSuccess(Track.Id trackId) {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
if (exportFileExists(track)) {
|
||||
public void onExportSuccess(ExportTask exportTask) {
|
||||
if (exportFileExists(exportTask)) {
|
||||
trackExportOverwrittenCount++;
|
||||
} else {
|
||||
trackExportSuccessCount++;
|
||||
}
|
||||
|
||||
onExportCompleted(track);
|
||||
nextExport(exportTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExportError(Track.Id trackId) {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
public void onExportError(ExportTask exportTask) {
|
||||
trackExportErrorCount++;
|
||||
trackErrors.add(track.getName());
|
||||
String name;
|
||||
if (exportTask.isMultiExport()) {
|
||||
name = exportTask.getFilename();
|
||||
} else {
|
||||
name = contentProviderUtils.getTrack(exportTask.getTrackIds().get(0)).getName();
|
||||
}
|
||||
trackErrors.add(name);
|
||||
|
||||
onExportCompleted(track);
|
||||
nextExport(exportTask);
|
||||
}
|
||||
|
||||
private void conflict(Track track) {
|
||||
PendingConflict newConflict = new PendingConflict(track);
|
||||
private void conflict(ExportTask exportTask) {
|
||||
PendingConflict newConflict = new PendingConflict(exportTask);
|
||||
conflictsQueue.add(newConflict);
|
||||
|
||||
if (conflictsQueue.size() == 1) {
|
||||
@@ -353,10 +364,10 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
* Handle conflicts (exporting file already exists).
|
||||
*/
|
||||
private class PendingConflict {
|
||||
private final Track track;
|
||||
private final ExportTask exportTask;
|
||||
|
||||
public PendingConflict(Track track) {
|
||||
this.track = track;
|
||||
public PendingConflict(ExportTask exportTask) {
|
||||
this.exportTask = exportTask;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,12 +379,18 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
public boolean resolve() {
|
||||
if (autoConflict == CONFLICT_NONE) {
|
||||
viewBinding.exportProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(ExportActivity.this, R.drawable.ic_report_problem_24));
|
||||
viewBinding.exportProgressAlertMsg.setText(getString(R.string.export_track_already_exists_msg, track.getName()));
|
||||
String name;
|
||||
if (exportTask.isMultiExport()) {
|
||||
name = exportTask.getFilename();
|
||||
} else {
|
||||
name = contentProviderUtils.getTrack(exportTask.getTrackIds().get(0)).getName();
|
||||
}
|
||||
viewBinding.exportProgressAlertMsg.setText(getString(R.string.export_track_already_exists_msg, name));
|
||||
setConflictVisibility(View.VISIBLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
export(track);
|
||||
export(exportTask);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -381,7 +398,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
* Overwrite the export file and set the autoConflict if user set the "do it for all" switch button.
|
||||
*/
|
||||
public void overwrite() {
|
||||
export(track, CONFLICT_OVERWRITE);
|
||||
export(exportTask, CONFLICT_OVERWRITE);
|
||||
|
||||
if (viewBinding.exportProgressApplyToAll.isChecked()) {
|
||||
autoConflict = CONFLICT_OVERWRITE;
|
||||
@@ -392,7 +409,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
* Skip the export file and set the autoConflict if user set the "do it for all" switch button.
|
||||
*/
|
||||
public void skip() {
|
||||
export(track, CONFLICT_SKIP);
|
||||
export(exportTask, CONFLICT_SKIP);
|
||||
|
||||
if (viewBinding.exportProgressApplyToAll.isChecked()) {
|
||||
autoConflict = CONFLICT_SKIP;
|
||||
|
||||
@@ -12,6 +12,9 @@ import androidx.annotation.NonNull;
|
||||
import androidx.core.app.JobIntentService;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
@@ -22,16 +25,14 @@ public class ExportService extends JobIntentService {
|
||||
private static final int JOB_ID = 1;
|
||||
|
||||
private static final String EXTRA_RECEIVER = "extra_receiver";
|
||||
private static final String EXTRA_TRACK_ID = "extra_track_id";
|
||||
private static final String EXTRA_TRACK_FILE_FORMAT = "extra_track_file_format";
|
||||
private static final String EXTRA_EXPORT_TASK = "export_task";
|
||||
private static final String EXTRA_DIRECTORY_URI = "extra_directory_uri";
|
||||
private static final String TAG = ExportService.class.getSimpleName();
|
||||
|
||||
public static void enqueue(Context context, ExportServiceResultReceiver receiver, Track.Id trackId, TrackFileFormat trackFileFormat, Uri directoryUri) {
|
||||
public static void enqueue(Context context, ExportServiceResultReceiver receiver, ExportTask exportTask, Uri directoryUri) {
|
||||
Intent intent = new Intent(context, JobService.class);
|
||||
intent.putExtra(EXTRA_RECEIVER, receiver);
|
||||
intent.putExtra(EXTRA_TRACK_ID, trackId);
|
||||
intent.putExtra(EXTRA_TRACK_FILE_FORMAT, trackFileFormat);
|
||||
intent.putExtra(EXTRA_EXPORT_TASK, exportTask);
|
||||
intent.putExtra(EXTRA_DIRECTORY_URI, directoryUri);
|
||||
enqueueWork(context, ExportService.class, JOB_ID, intent);
|
||||
}
|
||||
@@ -40,13 +41,12 @@ public class ExportService extends JobIntentService {
|
||||
protected void onHandleWork(@NonNull Intent intent) {
|
||||
// Get all data.
|
||||
ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_RECEIVER);
|
||||
Track.Id trackId = intent.getParcelableExtra(EXTRA_TRACK_ID);
|
||||
TrackFileFormat trackFileFormat = (TrackFileFormat) intent.getSerializableExtra(EXTRA_TRACK_FILE_FORMAT);
|
||||
ExportTask exportTask = intent.getParcelableExtra(EXTRA_EXPORT_TASK);
|
||||
Uri directoryUri = intent.getParcelableExtra(EXTRA_DIRECTORY_URI);
|
||||
|
||||
// Prepare resultCode and bundle to send to the receiver.
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID, trackId);
|
||||
bundle.putParcelable(ExportServiceResultReceiver.RESULT_EXTRA_EXPORT_TASK, exportTask);
|
||||
|
||||
// Build directory file.
|
||||
DocumentFile directoryFile = DocumentFile.fromTreeUri(this, directoryUri);
|
||||
@@ -57,9 +57,7 @@ public class ExportService extends JobIntentService {
|
||||
}
|
||||
|
||||
// Export.
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
boolean success = ExportUtils.exportTrack(this, trackFileFormat, directoryFile, track);
|
||||
boolean success = ExportUtils.exportTrack(this, directoryFile, exportTask);
|
||||
|
||||
// Send result to the receiver.
|
||||
int resultCode = success ? ExportServiceResultReceiver.RESULT_CODE_SUCCESS : ExportServiceResultReceiver.RESULT_CODE_ERROR;
|
||||
|
||||
+6
-6
@@ -17,7 +17,7 @@ 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_TRACK_ID = "result_extra_track_id";
|
||||
public static final String RESULT_EXTRA_EXPORT_TASK = "result_extra_export_task";
|
||||
|
||||
private final Receiver receiver;
|
||||
|
||||
@@ -28,19 +28,19 @@ public class ExportServiceResultReceiver extends ResultReceiver {
|
||||
|
||||
@Override
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
Track.Id trackId = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID);
|
||||
ExportTask exportTask = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_EXPORT_TASK);
|
||||
switch (resultCode) {
|
||||
case RESULT_CODE_SUCCESS -> receiver.onExportSuccess(trackId);
|
||||
case RESULT_CODE_ERROR -> receiver.onExportError(trackId);
|
||||
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(Track.Id trackId) {
|
||||
default void onExportSuccess(ExportTask exportTask) {
|
||||
}
|
||||
|
||||
default void onExportError(Track.Id trackId) {
|
||||
default void onExportError(ExportTask exportTask) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
|
||||
public class ExportTask implements Parcelable {
|
||||
|
||||
private final String filename; //TODO Name will only be used when exporting multiple tracks
|
||||
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
|
||||
private final List<Track.Id> trackIds;
|
||||
|
||||
public ExportTask(@Nullable String filename, @NonNull TrackFileFormat trackFileFormat, @NonNull List<Track.Id> trackIds) {
|
||||
this.filename = filename;
|
||||
this.trackFileFormat = trackFileFormat;
|
||||
this.trackIds = trackIds;
|
||||
}
|
||||
|
||||
protected ExportTask(Parcel in) {
|
||||
filename = in.readString();
|
||||
trackFileFormat = TrackFileFormat.valueOf(in.readString());
|
||||
trackIds = in.createTypedArrayList(Track.Id.CREATOR);
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
public TrackFileFormat getTrackFileFormat() {
|
||||
return trackFileFormat;
|
||||
}
|
||||
|
||||
public List<Track.Id> getTrackIds() {
|
||||
return trackIds;
|
||||
}
|
||||
|
||||
public boolean isMultiExport() {
|
||||
return trackIds.size() > 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ExportTask that = (ExportTask) o;
|
||||
return Objects.equals(filename, that.filename) && trackFileFormat == that.trackFileFormat && Objects.equals(trackIds, that.trackIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(filename, trackFileFormat, trackIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(filename);
|
||||
dest.writeString(trackFileFormat.name());
|
||||
dest.writeTypedList(trackIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Creator<ExportTask> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public ExportTask createFromParcel(Parcel in) {
|
||||
return new ExportTask(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExportTask[] newArray(int size) {
|
||||
return new ExportTask[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -99,11 +99,11 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
|
||||
return writeTrack(new Track[]{track}, outputStream);
|
||||
return writeTrack(List.of(track), outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
|
||||
public boolean writeTrack(List<Track> tracks, @NonNull OutputStream outputStream) {
|
||||
try {
|
||||
prepare(outputStream);
|
||||
writeHeader();
|
||||
|
||||
@@ -100,17 +100,17 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
|
||||
return writeTrack(new Track[]{track}, outputStream);
|
||||
return writeTrack(List.of(track), outputStream);
|
||||
}
|
||||
|
||||
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
|
||||
public boolean writeTrack(List<Track> tracks, @NonNull OutputStream outputStream) {
|
||||
try {
|
||||
prepare(outputStream);
|
||||
writeHeader(tracks);
|
||||
for (Track track : tracks) {
|
||||
writeMarkers(track);
|
||||
}
|
||||
boolean hasMultipleTracks = tracks.length > 1;
|
||||
boolean hasMultipleTracks = tracks.size() > 1;
|
||||
if (hasMultipleTracks) {
|
||||
writeMultiTrackBegin();
|
||||
}
|
||||
@@ -222,7 +222,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeHeader(Track[] tracks) {
|
||||
private void writeHeader(List<Track> tracks) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println(
|
||||
"""
|
||||
@@ -241,7 +241,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
printWriter.println("<open>1</open>");
|
||||
printWriter.println("<visibility>1</visibility>");
|
||||
|
||||
Track track = tracks[0];
|
||||
Track track = tracks.get(0);
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println("<atom:generator>" + StringUtils.formatCData(context.getString(R.string.app_name)) + "</atom:generator>");
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
@@ -65,11 +66,11 @@ public class KmzTrackExporter implements TrackExporter {
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
|
||||
return writeTrack(new Track[]{track}, outputStream);
|
||||
return writeTrack(List.of(track), outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
|
||||
public boolean writeTrack(List<Track> tracks, @NonNull OutputStream outputStream) {
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
|
||||
// Add kml file
|
||||
ZipEntry zipEntry = new ZipEntry(KMZ_KML_FILE);
|
||||
@@ -91,7 +92,7 @@ public class KmzTrackExporter implements TrackExporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void addImages(Context context, Track[] tracks, ZipOutputStream zipOutputStream) throws InterruptedException, IOException {
|
||||
private void addImages(Context context, List<Track> tracks, ZipOutputStream zipOutputStream) throws InterruptedException, IOException {
|
||||
for (Track track : tracks) {
|
||||
try (Cursor cursor = contentProviderUtils.getMarkerCursor(track.getId(), null, -1)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package de.dennisguse.opentracks.io.file.exporter;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
|
||||
@@ -29,7 +30,8 @@ import de.dennisguse.opentracks.data.models.Track;
|
||||
*/
|
||||
public interface TrackExporter {
|
||||
|
||||
boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream);
|
||||
boolean writeTrack(List<Track> tracks, @NonNull OutputStream outputStream);
|
||||
|
||||
@Deprecated // Only used for tests
|
||||
boolean writeTrack(Track track, @NonNull OutputStream outputStream);
|
||||
}
|
||||
|
||||
@@ -101,6 +101,30 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ExportDirectoryChooserOneFileActivity extends DirectoryChooserActivity {
|
||||
|
||||
@Override
|
||||
protected DocumentFile configureDirectoryChooserIntent(Intent intent) {
|
||||
super.configureDirectoryChooserIntent(intent);
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
return IntentUtils.toDocumentFile(this, PreferencesUtils.getDefaultExportDirectoryUri());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectoryValid(final DocumentFile directoryUri) {
|
||||
return super.isDirectoryValid(directoryUri) && directoryUri.canWrite();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent createNextActivityIntent(Uri directoryUri) {
|
||||
Intent intent = IntentUtils.newIntent(this, ExportActivity.class);
|
||||
intent.putExtra(ExportActivity.EXTRA_DIRECTORY_URI_KEY, directoryUri);
|
||||
intent.putExtra(ExportActivity.EXTRA_ONE_FILE_KEY, true);
|
||||
intent.putExtra(ExportActivity.EXTRA_TRACKFILEFORMAT_KEY, PreferencesUtils.getExportTrackFileFormat());
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefaultTrackExportDirectoryChooserActivity extends DirectoryChooserActivity {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
@@ -24,6 +25,7 @@ import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportService;
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportServiceResultReceiver;
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportTask;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
@@ -44,7 +46,7 @@ public class ExportUtils {
|
||||
|
||||
ExportServiceResultReceiver resultReceiver = new ExportServiceResultReceiver(new Handler(), new ExportServiceResultReceiver.Receiver() {
|
||||
@Override
|
||||
public void onExportError(Track.Id trackId) {
|
||||
public void onExportError(ExportTask unused) {
|
||||
Intent intent = new Intent(context, SettingsActivity.class);
|
||||
intent.putExtra(SettingsActivity.EXTRAS_CHECK_EXPORT_DIRECTORY, true);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
@@ -52,21 +54,28 @@ public class ExportUtils {
|
||||
}
|
||||
});
|
||||
|
||||
ExportService.enqueue(context, resultReceiver, trackId, trackFileFormat, directory.getUri());
|
||||
ExportService.enqueue(context, resultReceiver, new ExportTask(null, trackFileFormat, List.of(trackId)), directory.getUri());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean exportTrack(Context context, TrackFileFormat trackFileFormat, DocumentFile directory, Track track) {
|
||||
TrackExporter trackExporter = trackFileFormat.createTrackExporter(context, new ContentProviderUtils(context));
|
||||
public static boolean exportTrack(Context context, DocumentFile directory, ExportTask exportTask) {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
List<Track> tracks = exportTask.getTrackIds().stream().map(contentProviderUtils::getTrack).collect(Collectors.toList());
|
||||
Uri exportDocumentFileUri;
|
||||
if (tracks.size() == 1) {
|
||||
exportDocumentFileUri = getExportDocumentFileUri(context, tracks.get(0), exportTask.getTrackFileFormat(), directory);
|
||||
} else {
|
||||
exportDocumentFileUri = getExportDocumentFileUri(context, exportTask.getFilename(), exportTask.getTrackFileFormat(), directory);
|
||||
}
|
||||
|
||||
Uri exportDocumentFileUri = getExportDocumentFileUri(context, track, trackFileFormat, directory);
|
||||
if (exportDocumentFileUri == null) {
|
||||
Log.e(TAG, "Couldn't create document file for export");
|
||||
return false;
|
||||
}
|
||||
|
||||
TrackExporter trackExporter = exportTask.getTrackFileFormat().createTrackExporter(context, contentProviderUtils);
|
||||
try (OutputStream outputStream = context.getContentResolver().openOutputStream(exportDocumentFileUri)) {
|
||||
if (trackExporter.writeTrack(track, outputStream)) {
|
||||
if (trackExporter.writeTrack(tracks, outputStream)) {
|
||||
return true;
|
||||
} else {
|
||||
if (!DocumentFile.fromSingleUri(context, exportDocumentFileUri).delete()) {
|
||||
@@ -102,6 +111,10 @@ public class ExportUtils {
|
||||
|
||||
private static Uri getExportDocumentFileUri(Context context, Track track, TrackFileFormat trackFileFormat, DocumentFile directory) {
|
||||
String exportFileName = PreferencesUtils.getTrackFileformatGenerator().format(track, trackFileFormat);
|
||||
return getExportDocumentFileUri(context, exportFileName, trackFileFormat, directory);
|
||||
}
|
||||
|
||||
private static Uri getExportDocumentFileUri(Context context, String exportFileName, TrackFileFormat trackFileFormat, DocumentFile directory) {
|
||||
Uri exportDocumentFileUri = findFile(context, directory.getUri(), exportFileName);
|
||||
if (exportDocumentFileUri == null) {
|
||||
final DocumentFile file = directory.createFile(trackFileFormat.getMimeType(), exportFileName);
|
||||
|
||||
@@ -210,12 +210,12 @@ limitations under the License.
|
||||
<string name="export_cannot_write_to_dir">Cannot write to export directory.</string>
|
||||
<string name="export_dir_not_writable">" (not writable!)"</string>
|
||||
<string name="export_progress_message">Exporting to %1$s…</string>
|
||||
<string name="export_track_already_exists_msg">The track %1$s already exists at the destination directory.</string>
|
||||
<string name="export_track_already_exists_msg">The file %1$s already exists at the destination directory.</string>
|
||||
<string name="export_all_do_it_for_all">do it for all conflicts</string>
|
||||
<string name="export_track_errors">Tracks not exported</string>
|
||||
<string name="export_progress_summary_new_msg">New tracks exported</string>
|
||||
<string name="export_progress_summary_overwrite_msg">Tracks overwritten</string>
|
||||
<string name="export_progress_summary_skip_msg">Tracks skipped</string>
|
||||
<string name="export_track_errors">Files not exported</string>
|
||||
<string name="export_progress_summary_new_msg">New files exported</string>
|
||||
<string name="export_progress_summary_overwrite_msg">Files overwritten</string>
|
||||
<string name="export_progress_summary_skip_msg">Files skipped</string>
|
||||
<string name="export_progress_summary_errors_msg">Errors</string>
|
||||
<!-- External Storage -->
|
||||
<!-- Generic -->
|
||||
@@ -297,7 +297,8 @@ limitations under the License.
|
||||
<string name="menu_aggregated_statistics">Aggregated stats</string>
|
||||
<string name="menu_delete">Delete</string>
|
||||
<string name="menu_edit">Edit</string>
|
||||
<string name="menu_export_all">Export all</string>
|
||||
<string name="menu_export_all">Export all (one file per track)</string>
|
||||
<string name="menu_export_all_one_file">Export all (one file for all tracks)</string>
|
||||
<string name="menu_help">Help</string>
|
||||
<string name="menu_import_all">Import all</string>
|
||||
<string name="menu_insert_marker">Insert marker</string>
|
||||
|
||||
@@ -26,6 +26,13 @@
|
||||
android:targetClass="de.dennisguse.opentracks.io.file.importer.DirectoryChooserActivity$ExportDirectoryChooserActivity"
|
||||
android:targetPackage="@string/applicationId" />
|
||||
</Preference>
|
||||
<Preference
|
||||
android:key="@string/settings_export"
|
||||
android:title="@string/menu_export_all_one_file">
|
||||
<intent
|
||||
android:targetClass="de.dennisguse.opentracks.io.file.importer.DirectoryChooserActivity$ExportDirectoryChooserOneFileActivity"
|
||||
android:targetPackage="@string/applicationId" />
|
||||
</Preference>
|
||||
<Preference
|
||||
android:key="@string/settings_default_export_directory_key"
|
||||
android:title="@string/settings_default_export_uri_title">
|
||||
@@ -48,5 +55,4 @@
|
||||
android:title="@string/settings_export_filename_title"
|
||||
android:defaultValue="@string/export_filename_format_default" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
Reference in New Issue
Block a user