From 53599dae111d5830c5e67c1ea9569c0f22128b3d Mon Sep 17 00:00:00 2001 From: simonpoole Date: Fri, 25 Oct 2024 17:12:34 +0200 Subject: [PATCH 1/2] Generate an automatic summary of an upload Additionally optionally suppress warning about empty comments Resolves https://github.com/MarcusWolschon/osmeditor4android/issues/2392 --- .../de/blau/android/osm/TransferMenuTest.java | 18 +- .../blau/android/osm/UploadConflictTest.java | 4 +- src/main/java/de/blau/android/Logic.java | 4 +- .../blau/android/dialogs/ReviewAndUpload.java | 15 +- .../blau/android/listener/UploadListener.java | 297 ++++++++++++++++-- .../java/de/blau/android/osm/Changeset.java | 42 +-- .../java/de/blau/android/osm/UndoStorage.java | 4 +- .../de/blau/android/prefs/Preferences.java | 29 +- src/main/res/layout/upload_tabs.xml | 17 + src/main/res/values/prefkeys.xml | 1 + src/main/res/values/strings.xml | 10 + src/main/res/values/tipkeys.xml | 1 + 12 files changed, 382 insertions(+), 60 deletions(-) diff --git a/src/androidTest/java/de/blau/android/osm/TransferMenuTest.java b/src/androidTest/java/de/blau/android/osm/TransferMenuTest.java index f1973a9159..6994d38be6 100644 --- a/src/androidTest/java/de/blau/android/osm/TransferMenuTest.java +++ b/src/androidTest/java/de/blau/android/osm/TransferMenuTest.java @@ -20,6 +20,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.xml.sax.SAXException; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; import com.orhanobut.mockwebserverplus.MockWebServerPlus; @@ -38,10 +40,10 @@ import de.blau.android.Logic; import de.blau.android.Main; import de.blau.android.R; -import de.blau.android.R.string; import de.blau.android.SignalHandler; import de.blau.android.SignalUtils; import de.blau.android.TestUtils; +import de.blau.android.listener.UploadListener; import de.blau.android.prefs.API; import de.blau.android.prefs.AdvancedPrefDatabase; import de.blau.android.prefs.Preferences; @@ -133,6 +135,8 @@ public void dataUpload() { fail(e1.getMessage()); } UploadConflictTest.fillCommentAndSource(instrumentation, device); + uiSelector = new UiSelector().className("android.widget.Button").instance(1); // dialog upload button + button = device.findObject(uiSelector); try { button.clickAndWaitForNewWindow(); } catch (UiObjectNotFoundException e1) { @@ -152,10 +156,12 @@ public void dataUpload() { mockServer.takeRequest(); // capabilities query RecordedRequest recordedRequest = mockServer.takeRequest(); // changeset // this currently doesn't work - // Changeset changeset = Changeset.parse(XmlPullParserFactory.newInstance().newPullParser(), - // new ByteArrayInputStream(recordedRequest.getBody().readByteArray())); - // assertEquals(COMMENT_1, changeset.tags.get("comment")); - // assertEquals(SOURCE_1, changeset.tags.get("source")); + byte[] request = recordedRequest.getBody().readByteArray(); + Changeset changeset = Changeset.parse(XmlPullParserFactory.newInstance().newPullParser(), new ByteArrayInputStream(request)); + assertEquals(UploadConflictTest.COMMENT_1, changeset.getTags().get(Tags.KEY_COMMENT)); + assertEquals("1 Tertiary", changeset.getTags().get(UploadListener.V_CREATED)); + assertEquals("1 untagged way", changeset.getTags().get(UploadListener.V_DELETED)); + assertTrue(changeset.getTags().get(UploadListener.V_MODIFIED).contains("2 unknown object to Public transport route (Legacy)")); recordedRequest = mockServer.takeRequest(); // diff upload OsmChangeParser ocParser = new OsmChangeParser(); ocParser.start(new ByteArrayInputStream(recordedRequest.getBody().readByteArray())); @@ -171,7 +177,7 @@ public void dataUpload() { Way w = (Way) storage.getOsmElement(Way.NAME, 210461100); assertNotNull(w); assertEquals(OsmElement.STATE_DELETED, w.getState()); - } catch (InterruptedException | SAXException | IOException | ParserConfigurationException e) { + } catch (InterruptedException | SAXException | IOException | ParserConfigurationException | XmlPullParserException e) { fail(e.getMessage()); } } diff --git a/src/androidTest/java/de/blau/android/osm/UploadConflictTest.java b/src/androidTest/java/de/blau/android/osm/UploadConflictTest.java index 2196fbce5c..1ee30a5d9c 100644 --- a/src/androidTest/java/de/blau/android/osm/UploadConflictTest.java +++ b/src/androidTest/java/de/blau/android/osm/UploadConflictTest.java @@ -55,8 +55,8 @@ public class UploadConflictTest { public static final int TIMEOUT = 90; - private static final String SOURCE_1 = "source 1"; - private static final String COMMENT_1 = "comment 1"; + static final String SOURCE_1 = "source 1"; + static final String COMMENT_1 = "comment 1"; MockWebServerPlus mockServer = null; Context context = null; diff --git a/src/main/java/de/blau/android/Logic.java b/src/main/java/de/blau/android/Logic.java index b7a91c0c87..edfbed0799 100644 --- a/src/main/java/de/blau/android/Logic.java +++ b/src/main/java/de/blau/android/Logic.java @@ -4447,7 +4447,9 @@ protected UploadResult doInBackground(Void params) { result.setError(ErrorCodes.API_OFFLINE); return result; } - getDelegator().uploadToServer(server, comment, source, closeOpenChangeset, closeChangeset, extraTags, elements); + // set comment here if empty to avoid saving it + getDelegator().uploadToServer(server, Util.isEmpty(comment) ? activity.getString(R.string.upload_auto_summary) : comment, source, + closeOpenChangeset, closeChangeset, extraTags, elements); } catch (final OsmServerException e) { int errorCode = e.getHttpErrorCode(); result.setHttpError(errorCode); diff --git a/src/main/java/de/blau/android/dialogs/ReviewAndUpload.java b/src/main/java/de/blau/android/dialogs/ReviewAndUpload.java index 4d71143854..e5dfe8e129 100644 --- a/src/main/java/de/blau/android/dialogs/ReviewAndUpload.java +++ b/src/main/java/de/blau/android/dialogs/ReviewAndUpload.java @@ -19,6 +19,8 @@ import android.widget.AutoCompleteTextView; import android.widget.Button; import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.ImageButton; import android.widget.TextView; @@ -174,6 +176,13 @@ public AppCompatDialog onCreateDialog(Bundle savedInstanceState) { closeChangeset.setChecked(prefs.closeChangesetOnSave()); CheckBox requestReview = (CheckBox) layout.findViewById(R.id.upload_request_review); + CheckBox emptyCommentWarning = (CheckBox) layout.findViewById(R.id.upload_empty_comment_warning); + emptyCommentWarning.setChecked(prefs.emptyCommentWarning()); + emptyCommentWarning.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { + prefs.setEmptyCommentWarning(isChecked); + Tip.showDialog(getActivity(), R.string.tip_empty_comment_warning_key, R.string.tip_empty_comment_warning); + }); + comment = (AutoCompleteTextView) layout.findViewById(R.id.upload_comment); List comments = new ArrayList<>(App.getLogic().getLastComments()); FilterlessArrayAdapter commentAdapter = new FilterlessArrayAdapter<>(activity, android.R.layout.simple_dropdown_item_1line, comments); @@ -211,17 +220,13 @@ public AppCompatDialog onCreateDialog(Bundle savedInstanceState) { source.requestFocus(); }); - FormValidation commentValidator = new NotEmptyValidator(comment, getString(R.string.upload_validation_error_empty_comment)); - FormValidation sourceValidator = new NotEmptyValidator(source, getString(R.string.upload_validation_error_empty_source)); - List validators = Arrays.asList(commentValidator, sourceValidator); - builder.setPositiveButton(R.string.transfer_download_current_upload, null); builder.setNegativeButton(R.string.no, (dialog, which) -> saveCommentAndSource(comment, source)); AlertDialog dialog = builder.create(); dialog.setOnShowListener( - new UploadListener(activity, comment, source, openChangeset ? closeOpenChangeset : null, closeChangeset, requestReview, validators, elements)); + new UploadListener(activity, comment, source, openChangeset ? closeOpenChangeset : null, closeChangeset, requestReview, elements)); return dialog; } diff --git a/src/main/java/de/blau/android/listener/UploadListener.java b/src/main/java/de/blau/android/listener/UploadListener.java index a17b8031ac..b8bfb2b17e 100755 --- a/src/main/java/de/blau/android/listener/UploadListener.java +++ b/src/main/java/de/blau/android/listener/UploadListener.java @@ -1,8 +1,13 @@ package de.blau.android.listener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import android.content.DialogInterface; import android.os.SystemClock; @@ -20,12 +25,24 @@ import de.blau.android.R; import de.blau.android.dialogs.ErrorAlert; import de.blau.android.dialogs.ReviewAndUpload; +import de.blau.android.osm.Capabilities; +import de.blau.android.osm.Node; import de.blau.android.osm.OsmElement; +import de.blau.android.osm.Relation; import de.blau.android.osm.Server; import de.blau.android.osm.Tags; +import de.blau.android.osm.UndoStorage; +import de.blau.android.osm.UndoStorage.UndoElement; +import de.blau.android.osm.UndoStorage.UndoNode; +import de.blau.android.osm.UndoStorage.UndoRelation; +import de.blau.android.osm.UndoStorage.UndoWay; +import de.blau.android.osm.Way; +import de.blau.android.presets.Preset; +import de.blau.android.presets.PresetItem; import de.blau.android.tasks.TransferTasks; import de.blau.android.util.ScreenMessage; import de.blau.android.validation.FormValidation; +import de.blau.android.validation.NotEmptyValidator; /** * @author mb @@ -33,6 +50,13 @@ */ public class UploadListener implements DialogInterface.OnShowListener, View.OnClickListener { + // auto summary tags + public static final String V_DELETED = "v:deleted"; + public static final String V_MODIFIED_MEMBERS = "v:modified_members"; + public static final String V_MODIFIED_GEOMETRY = "v:modified_geometry"; + public static final String V_MODIFIED = "v:modified"; + public static final String V_CREATED = "v:created"; + private static final long DEBOUNCE_TIME = 1000; private final FragmentActivity caller; @@ -56,20 +80,22 @@ public class UploadListener implements DialogInterface.OnShowListener, View.OnCl * @param closeOpenChangeset close any open changeset first if true * @param closeChangeset close the changeset after upload if true * @param requestReview CheckBox for the review_requested tag - * @param validations a List of validations to perform on the form fields * @param elements List of OsmELement to upload if null all changed elements will be uploaded */ public UploadListener(@NonNull final FragmentActivity caller, @NonNull final EditText commentField, @NonNull final EditText sourceField, @Nullable CheckBox closeOpenChangeset, @NonNull final CheckBox closeChangeset, @NonNull CheckBox requestReview, - @NonNull final List validations, List elements) { + @Nullable List elements) { + this.caller = caller; this.commentField = commentField; this.sourceField = sourceField; this.closeOpenChangeset = closeOpenChangeset; this.closeChangeset = closeChangeset; this.requestReview = requestReview; - this.validations = validations; this.elements = elements; + FormValidation commentValidator = new NotEmptyValidator(commentField, caller.getString(R.string.upload_validation_error_empty_comment)); + FormValidation sourceValidator = new NotEmptyValidator(sourceField, caller.getString(R.string.upload_validation_error_empty_source)); + this.validations = Arrays.asList(commentValidator, sourceValidator); } @Override @@ -83,8 +109,11 @@ public void onClick(View view) { if (lastClickTime != null && (SystemClock.elapsedRealtime() - lastClickTime < DEBOUNCE_TIME)) { return; } - validateFields(); - if (tagsShown || ReviewAndUpload.getPage(caller) == ReviewAndUpload.TAGS_PAGE) { + final boolean emptyCommentWarning = App.getPreferences(caller).emptyCommentWarning(); + if (emptyCommentWarning) { + validateFields(); + } + if (!emptyCommentWarning || tagsShown || ReviewAndUpload.getPage(caller) == ReviewAndUpload.TAGS_PAGE) { ReviewAndUpload.dismissDialog(caller); upload(); } else { @@ -98,29 +127,254 @@ public void onClick(View view) { * Actually upload */ private void upload() { - Map extraTags = new HashMap<>(); + Map extraTags = new LinkedHashMap<>(); if (requestReview.isChecked()) { extraTags.put(Tags.KEY_REVIEW_REQUESTED, Tags.VALUE_YES); } + extraTags.putAll(generateAutoSummary(elements != null ? elements : App.getDelegator().listChangedElements())); final Logic logic = App.getLogic(); final Server server = logic.getPrefs().getServer(); - if (server.isLoginSet()) { - boolean hasDataChanges = logic.hasChanges(); - boolean hasBugChanges = !App.getTaskStorage().isEmpty() && App.getTaskStorage().hasChanges(); - if (hasDataChanges || hasBugChanges) { - if (hasDataChanges) { - logic.upload(caller, getString(commentField), getString(sourceField), closeOpenChangeset != null && closeOpenChangeset.isChecked(), - closeChangeset.isChecked(), extraTags, elements, () -> logic.checkForMail(caller, server)); - } - if (hasBugChanges) { - TransferTasks.upload(caller, server, null); - } - } else { - ScreenMessage.barInfo(caller, R.string.toast_no_changes); + if (!server.isLoginSet()) { + ErrorAlert.showDialog(caller, ErrorCodes.NO_LOGIN_DATA); + return; + } + boolean hasDataChanges = logic.hasChanges(); + boolean hasBugChanges = !App.getTaskStorage().isEmpty() && App.getTaskStorage().hasChanges(); + if (hasDataChanges || hasBugChanges) { + if (hasDataChanges) { + logic.upload(caller, getTrimmedString(commentField), getTrimmedString(sourceField), + closeOpenChangeset != null && closeOpenChangeset.isChecked(), closeChangeset.isChecked(), extraTags, elements, + () -> logic.checkForMail(caller, server)); + } + if (hasBugChanges) { + TransferTasks.upload(caller, server, null); } } else { - ErrorAlert.showDialog(caller, ErrorCodes.NO_LOGIN_DATA); + ScreenMessage.barInfo(caller, R.string.toast_no_changes); + } + } + + /** + * Container for actions, currently simply strings + */ + private class Actions { + List created = new ArrayList<>(); + List modified = new ArrayList<>(); + List geometryModified = new ArrayList<>(); + List membersModified = new ArrayList<>(); + List deleted = new ArrayList<>(); + } + + /** + * Generate tags that roughly describe what we've done + * + * @param elements list of OsmElements that will be uploaded + * @return a Map containing the tags + */ + private Map generateAutoSummary(@NonNull List elements) { + Map result = new HashMap<>(); + UndoStorage undoStorage = App.getDelegator().getUndo(); + Preset[] presets = App.getCurrentPresets(caller); + Actions actions = new Actions(); + for (OsmElement current : elements) { + addActionsForElement(actions, undoStorage, presets, current); + } + putSummary(V_CREATED, actions.created, result); + putSummary(V_MODIFIED, actions.modified, result); + putSummary(V_MODIFIED_GEOMETRY, actions.geometryModified, result); + putSummary(V_MODIFIED_MEMBERS, actions.membersModified, result); + putSummary(V_DELETED, actions.deleted, result); + return result; + } + + /** + * Given an OsmELement generate a rough description of what changes were made in this session + * + * Note has the side effect of adding changes for ways if way nodes were changed + * + * @param actions container for the list of actions + * @param undoStorage our current UndoStorage + * @param presets current presets + * @param element the OsmElement + */ + private void addActionsForElement(@NonNull Actions actions, @NonNull UndoStorage undoStorage, @NonNull Preset[] presets, @NonNull OsmElement element) { + + final boolean hasTags = element.hasTags(); + final Map currentTags = element.getTags(); + PresetItem matchCurrent = Preset.findBestMatch(caller, presets, currentTags, null, element, true); + final String currentPresetName = getPresetName(matchCurrent); + final boolean currentHasMatch = matchCurrent != null; + + if (OsmElement.STATE_CREATED == element.getState()) { + // created + if (hasTags) { + actions.created.add(currentPresetName); + } else { + actions.created.add(getUntaggedString(element)); + } + return; + } + // note original can be null if data was loaded from a JOSM OSM file + UndoElement original = undoStorage.getOriginal(element); + final Map originalTags = original != null ? original.getTags() : new HashMap<>(); + // element type should be the same as current element here + PresetItem matchOriginal = Preset.findBestMatch(presets, originalTags, null, element.getType(), true, null); + if (OsmElement.STATE_DELETED == element.getState()) { + // deleted + actions.deleted.add(!originalTags.isEmpty() ? getPresetName(matchOriginal) : getUntaggedString(element)); + return; + } + addGeometryActions(actions, presets, element, original, hasTags, currentPresetName); + if (!hasTags) { + return; + } + // tag changes + final boolean originalHasMatch = matchOriginal != null; + if ((!originalHasMatch && currentHasMatch) || (originalHasMatch && !matchOriginal.equals(matchCurrent))) { + actions.modified.add(caller.getString(R.string.changed_preset, getPresetName(matchOriginal), currentPresetName)); + } else if (!originalTags.equals(currentTags)) { + actions.modified.add(caller.getString(R.string.changed_tags, currentPresetName)); + } + } + + /** + * Add geometry changes for element + * + * @param actions container for the list of actions + * @param presets current presets + * @param element the current element + * @param original the elements original state + * @param hasTags true if element has tags + * @param currentPresetName the name of the preset for element + */ + private void addGeometryActions(@NonNull Actions actions, @NonNull Preset[] presets, @NonNull OsmElement element, @Nullable UndoElement original, + final boolean hasTags, @NonNull final String currentPresetName) { + if (original == null) { + return; + } + // geometry changes + if (element instanceof Node && moved((Node) element, (UndoNode) original)) { + if (hasTags) { + actions.geometryModified.add(caller.getString(R.string.moved, currentPresetName)); + } + addModifiedWays(presets, (Node) element, actions.geometryModified); + } + if (element instanceof Way && !((UndoWay) original).getNodes().equals(((Way) element).getNodes())) { + actions.geometryModified.add(currentPresetName); + } + if (element instanceof Relation && !((UndoRelation) original).getMembers().equals(((Relation) element).getMembers())) { + actions.membersModified.add(currentPresetName); + } + } + + /** + * Add actions for all ways modified by moving a node + * + * @param presets current presets + * @param node the Node + * @param geometryModified set of actions to return + */ + private void addModifiedWays(@NonNull Preset[] presets, @NonNull Node node, @NonNull Collection geometryModified) { + for (Way w : App.getLogic().getWaysForNode(node)) { + PresetItem match = Preset.findBestMatch(caller, presets, w.getTags(), null, w, true); + geometryModified.add(getPresetName(match)); + } + } + + /** + * Get a string for an untagged element + * + * @param e an OsmElement + * @return a suitable string + */ + @NonNull + private String getUntaggedString(@NonNull OsmElement e) { + switch (e.getName()) { + case Node.NAME: + return caller.getString(R.string.untagged_node); + case Way.NAME: + return caller.getString(R.string.untagged_way); + case Relation.NAME: + return caller.getString(R.string.untagged_relation); + default: // fall through + } + throw new IllegalArgumentException(e.getClass().getCanonicalName() + " is unknown"); + } + + /** + * Check if a node has moved + * + * @param current current Node + * @param original original UndoNode + * @return true if the node moved + */ + private boolean moved(@NonNull Node current, @NonNull UndoNode original) { + return current.getLat() != original.getLat() || current.getLon() != original.getLon(); + } + + /** + * Create tags(s) for the summary taking max length in to account + * + * @param key tag key + * @param actions list of actions we are summarizing + * @param result Map holding the tags + */ + private void putSummary(@NonNull String key, @NonNull Collection actions, @NonNull Map result) { + actions = addCounts(actions); + int summaryCount = 0; + StringBuilder summary = new StringBuilder(); + for (String s : actions) { + if (summary.length() + s.length() + 1 > Capabilities.DEFAULT_MAX_STRING_LENGTH) { + result.put(key + (summaryCount > 0 ? summaryCount : ""), summary.toString()); + summaryCount++; + summary.setLength(0); + } + if (summary.length() != 0) { + summary.append("\n"); + } + summary.append(s); + } + if (summary.length() > 0) { + result.put(key + (summaryCount > 0 ? summaryCount : ""), summary.toString()); + } + } + + /** + * Generate a List of actions with counts from the original actions + * + * @param actions the actions + * @return a List of Strings with counts + */ + @NonNull + List addCounts(@NonNull Collection actions) { + List result = new ArrayList<>(); + Map counts = new HashMap<>(); + for (String action : actions) { + Integer count = counts.get(action); + if (count == null) { + counts.put(action, 1); + continue; + } + counts.put(action, count + 1); + } + for (Entry entry : counts.entrySet()) { + result.add(Integer.toString(entry.getValue()) + " " + entry.getKey()); + } + return result; + } + + /** + * Get the preset name from a preset item + * + * @param item the PresetItem + * @return a name + */ + @NonNull + private String getPresetName(@Nullable PresetItem item) { + if (item != null) { + return item.getName(); } + return caller.getString(R.string.unknown_object); } /** @@ -129,7 +383,8 @@ private void upload() { * @param editText the EditText * @return a String with the EditText contents */ - String getString(@NonNull EditText editText) { + @NonNull + private String getTrimmedString(@NonNull EditText editText) { return editText.getText().toString().trim(); } diff --git a/src/main/java/de/blau/android/osm/Changeset.java b/src/main/java/de/blau/android/osm/Changeset.java index f4d21dfda0..9ce601ddbf 100644 --- a/src/main/java/de/blau/android/osm/Changeset.java +++ b/src/main/java/de/blau/android/osm/Changeset.java @@ -137,28 +137,32 @@ static Changeset parse(@NonNull XmlPullParser parser, @NonNull InputStream is) t Changeset result = new Changeset(); while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { String tagName = parser.getName(); - if (eventType == XmlPullParser.START_TAG) { - switch (tagName) { - case OsmXml.CHANGESET: - result.open = TRUE.equals(parser.getAttributeValue(null, OPEN_ATTR)); - try { - result.osmId = Long.parseLong(parser.getAttributeValue(null, ID_ATTR)); - String changesStr = parser.getAttributeValue(null, CHANGES_COUNT_ATTR); - result.changes = Integer.parseInt(changesStr); - } catch (NumberFormatException nex) { - throw new XmlPullParserException(nex.getMessage()); - } - break; - case OsmElement.TAG: - String k = parser.getAttributeValue(null, OsmElement.TAG_KEY_ATTR); - String v = parser.getAttributeValue(null, OsmElement.TAG_VALUE_ATTR); - result.getTags().put(k, v); + if (eventType != XmlPullParser.START_TAG) { + continue; + } + switch (tagName) { + case OsmXml.CHANGESET: + if (parser.getAttributeCount() < 3) { // in testing these won't be available break; - default: - // nothing } - Log.d(DEBUG_TAG, "#" + result.osmId + " is " + (result.isOpen() ? "open" : "closed")); + result.open = TRUE.equals(parser.getAttributeValue(null, OPEN_ATTR)); + try { + result.osmId = Long.parseLong(parser.getAttributeValue(null, ID_ATTR)); + String changesStr = parser.getAttributeValue(null, CHANGES_COUNT_ATTR); + result.changes = Integer.parseInt(changesStr); + } catch (NumberFormatException | NullPointerException ex) { + throw new XmlPullParserException(ex.getMessage()); + } + break; + case OsmElement.TAG: + String k = parser.getAttributeValue(null, OsmElement.TAG_KEY_ATTR); + String v = parser.getAttributeValue(null, OsmElement.TAG_VALUE_ATTR); + result.tags.put(k, v); + break; + default: + // nothing } + Log.d(DEBUG_TAG, "#" + result.osmId + " is " + (result.isOpen() ? "open" : "closed")); } return result; } diff --git a/src/main/java/de/blau/android/osm/UndoStorage.java b/src/main/java/de/blau/android/osm/UndoStorage.java index bdb145fde2..8bb29321cb 100644 --- a/src/main/java/de/blau/android/osm/UndoStorage.java +++ b/src/main/java/de/blau/android/osm/UndoStorage.java @@ -460,7 +460,7 @@ public boolean canRedo() { * * The checkpoint can later be restored using {@link #restore(Checkpoint)}. */ - class Checkpoint implements Serializable { + public class Checkpoint implements Serializable { private static final long serialVersionUID = 2L; private final Map elements = new HashMap<>(); @@ -1168,7 +1168,7 @@ public List getRedoCheckpoints(@NonNull OsmElement element) { } /** - * Get a list of all UndoElements for a specific element + * Get a list of all UndoElements for a specific element, more recent first, oldest last * * @param checkpoints list of checkpoints * @param element the element diff --git a/src/main/java/de/blau/android/prefs/Preferences.java b/src/main/java/de/blau/android/prefs/Preferences.java index b5f22fa722..b74cc2f66d 100755 --- a/src/main/java/de/blau/android/prefs/Preferences.java +++ b/src/main/java/de/blau/android/prefs/Preferences.java @@ -82,6 +82,7 @@ public class Preferences { private final boolean nameSuggestionPresetsEnabled; private final boolean autoApplyPreset; private final boolean closeChangesetOnSave; + private boolean emptyCommentWarning; private final boolean splitActionBarEnabled; private String gpsSource; private final String gpsTcpSource; @@ -215,6 +216,7 @@ public Preferences(@NonNull Context ctx) { nameSuggestionPresetsEnabled = prefs.getBoolean(r.getString(R.string.config_enableNameSuggestionsPresets_key), true); autoApplyPreset = prefs.getBoolean(r.getString(R.string.config_autoApplyPreset_key), true); closeChangesetOnSave = prefs.getBoolean(r.getString(R.string.config_closeChangesetOnSave_key), true); + emptyCommentWarning = prefs.getBoolean(r.getString(R.string.config_emptyCommentWarning_key), true); splitActionBarEnabled = prefs.getBoolean(r.getString(R.string.config_splitActionBarEnabled_key), true); scaleLayer = prefs.getString(r.getString(R.string.config_scale_key), r.getString(R.string.scale_metric)); mapProfile = prefs.getString(r.getString(R.string.config_mapProfile_key), null); @@ -344,7 +346,7 @@ public Preferences(@NonNull Context ctx) { minCircleSegment = getFloatFromStringPref(R.string.config_minCircleSegment_key, 0.5f); poiKeys = prefs.getStringSet(r.getString(R.string.config_poi_keys_key), new HashSet<>(Arrays.asList(r.getStringArray(R.array.poi_keys_defaults)))); - + replaceTolerance = getFloatFromStringPref(R.string.config_replaceTolerance_key, 1.0f); } @@ -1934,13 +1936,32 @@ public boolean zoomWithKeys() { } /** - * Enable/diable the zoom with keys preference + * Enable/disable the zoom with keys preference * * @param enable value to set */ public void setZoomWithKeys(boolean enable) { zoomWithKeys = enable; - prefs.edit().putBoolean(r.getString(R.string.config_zoomWithKeys_key), enable).commit(); + putBoolean(r.getString(R.string.config_zoomWithKeys_key), enable); + } + + /** + * Warn if the comment field is empty on upload + * + * @return true is we should warn + */ + public boolean emptyCommentWarning() { + return emptyCommentWarning; + } + + /** + * Set if we should warn if the comment field is empty on upload + * + * @param enable if true show a warning + */ + public void setEmptyCommentWarning(boolean enable) { + emptyCommentWarning = enable; + putBoolean(r.getString(R.string.config_emptyCommentWarning_key), enable); } /** @@ -1979,7 +2000,7 @@ public double getMinCircleSegment() { public Set poiKeys() { return poiKeys; } - + /** * Get the max distance a tagged node can be moved when replacing a way geometry * diff --git a/src/main/res/layout/upload_tabs.xml b/src/main/res/layout/upload_tabs.xml index 8dc40fa624..97c16e2b66 100644 --- a/src/main/res/layout/upload_tabs.xml +++ b/src/main/res/layout/upload_tabs.xml @@ -172,6 +172,23 @@ android:layout_below="@id/upload_close_changeset" android:gravity="center_vertical" android:text="" /> + + diff --git a/src/main/res/values/prefkeys.xml b/src/main/res/values/prefkeys.xml index f4a7bb76c4..bcf2308e2e 100644 --- a/src/main/res/values/prefkeys.xml +++ b/src/main/res/values/prefkeys.xml @@ -50,6 +50,7 @@ enableNameSuggestionsPresets autoApplyPreset closeChangesetOnSave + emptyCommentWarning splitActionBarEnabled osmWiki offsetServer2 diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 81830f51b1..af452d0231 100755 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -182,6 +182,15 @@ Request review: Comment should not be empty Source should not be empty + Warn if comment is empty + Automatically generated summary + untagged node + untagged way + untagged relation + %1$s to %2$s + %1$s changed tags + %1$s moved + unknown object Review changes @@ -591,6 +600,7 @@ When multiple OSM elements or other objects are very close together or elements are members of relations the disambiguation menu will be displayed. You can then select the specific element from the list.<p>If an element is currently already selected it will be highlighted. On devices running Android 10 or later we need to copy files to be able to access them fully. The copies are located in <i>Downloads/Vespucci/imports</i>. If necessary you can delete the originals and just retain the copies. WMS servers can support larger tile sizes than 256x256, consider using 512x512 for a more efficient configuration. + Vespucci always adds an automatically created summary of what you uploaded.<p>You should still consider adding a few words on why you are making the edits to provide more context for other mappers. + - diff --git a/src/main/res/values/tipkeys.xml b/src/main/res/values/tipkeys.xml index 92877f60ad..7f90a9ce07 100644 --- a/src/main/res/values/tipkeys.xml +++ b/src/main/res/values/tipkeys.xml @@ -24,4 +24,5 @@ disambiguationMenu fileImport wmsTileSize + emptyCommentWarning \ No newline at end of file From 2ebd876c7a81ec18f16bc2bd5f4b4b751f9742f6 Mon Sep 17 00:00:00 2001 From: simonpoole Date: Sat, 2 Nov 2024 10:27:51 +0100 Subject: [PATCH 2/2] Add documentation on the upload modal --- documentation/docs/help/en/Custom imagery.md | 2 +- documentation/docs/help/en/Introduction.md | 4 +-- .../docs/help/en/Main map display.md | 10 ++++-- .../docs/help/en/Uploading your changes.md | 33 +++++++++++++++++++ documentation/mkdocs.yml | 1 + src/main/assets/help/en/Custom imagery.html | 2 +- src/main/assets/help/en/Introduction.html | 4 +-- src/main/assets/help/en/Main map display.html | 7 ++-- .../help/en/Uploading your changes.html | 28 ++++++++++++++++ .../blau/android/dialogs/ReviewAndUpload.java | 4 --- src/main/res/values/helptopics.xml | 3 ++ 11 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 documentation/docs/help/en/Uploading your changes.md create mode 100644 src/main/assets/help/en/Uploading your changes.html diff --git a/documentation/docs/help/en/Custom imagery.md b/documentation/docs/help/en/Custom imagery.md index 42f7881576..4d8093cad5 100644 --- a/documentation/docs/help/en/Custom imagery.md +++ b/documentation/docs/help/en/Custom imagery.md @@ -6,7 +6,7 @@ * imagery layers in [MBTiles](https://github.com/mapbox/mbtiles-spec) and [PMtiles V3](https://github.com/protomaps/PMTiles/blob/main/spec/v3/spec.md) format on device * WMS servers supporting images in EPSG:3857 (including alternative names like EPSG:900913 etc) and EPSG:4326 projections -Besides manually adding layers you can add WMS layers by querying a WMS server for a list of supported layers see [layer control](Main%20map%20display.md#Layer%20control), or by querying the Open Aerial Map (OAM) service. +Besides manually adding layers you can add WMS layers by querying a WMS server for a list of supported layers see [layer control](Main%20map%20display.md#layer_control), or by querying the Open Aerial Map (OAM) service. __NOTE__ even though the URLs may look similar to those for a WMS layer, we do not support requesting data from ESRI MapServer with their native protocol. diff --git a/documentation/docs/help/en/Introduction.md b/documentation/docs/help/en/Introduction.md index f901ce5935..7519462c0a 100644 --- a/documentation/docs/help/en/Introduction.md +++ b/documentation/docs/help/en/Introduction.md @@ -165,9 +165,9 @@ When the red lock is displayed all non-editing actions are available. Additional Select the same button or menu item you did for the download and now select "Upload data to OSM server". -Vespucci supports OAuth authorization and the classical username and password method. OAuth is preferable since it avoids sending passwords in the clear. +Vespucci supports OAuth 2, OAuth 1.0a authorization and the classical username and password method. Since July 1st 2024 the standard OpenStreetMap API only supports OAuth 2 and other methods are only available on private installations of the API or other projects that have repurposed OSM software. -New Vespucci installs will have OAuth enabled by default. On your first attempt to upload modified data, a page from the OSM website loads. After you have logged on (over an encrypted connection) you will be asked to authorize Vespucci to edit using your account. If you want to or need to authorize the OAuth access to your account before editing there is a corresponding item in the "Tools" menu. +Authorizing Vespucci to access your account on your behalf requires you to one time login with your display name and password. If your Vespucci install isn't authorized when you attempt to upload modified data you will be asked to login to the OSM website (over an encrypted connection). After you have logged on you will be asked to authorize Vespucci to edit using your account. If you want to or need to authorize the OAuth access to your account before editing there is a corresponding item in the "Tools" menu. If you want to save your work and do not have Internet access, you can save to a JOSM compatible .osm file and either upload later with Vespucci or with JOSM. diff --git a/documentation/docs/help/en/Main map display.md b/documentation/docs/help/en/Main map display.md index bdae8ab135..f61c78dbb8 100644 --- a/documentation/docs/help/en/Main map display.md +++ b/documentation/docs/help/en/Main map display.md @@ -37,6 +37,8 @@ _shop_, _amenity_, _leisure_, _tourism_, _craft_, _office_ or _emergency_. If an Tapping an entry in the display will center the map on the object and select it, tapping it a second time (just as on the map display) will start the property editor, in _Tag only_ mode the property editor will start directly as expected. The POI entries are highlighted with the same validation indication as map icons, see [validation styling](https://github.com/MarcusWolschon/osmeditor4android/blob/master/src/main/assets/styles/Color-round.xml#L39). + + ### Layer control Vespucci currently supports multiple tiled background imagery layers, multiple tiled overlay layers (both raster and Mapbox vector tiles), a grid/scale layer, a task layer, a photo layer, multiple GeoJSON layers, multiple GPX/GPS layers and, naturally, an OSM data layer. Tapping the layer control (upper right corner) will display the layer dialog). @@ -85,7 +87,7 @@ The layer dialog supports the following actions on the layer entries: * __Configure...__ Change layer settings * __Discard__ Turn this layer off. For the task layer this will free resources if the app is exited and re-started. * Data layer: - * __Configure...__ Select the API instance, configure the URLs including read-only sources and authentication method. Basic Authentication, OAuth 1.0a and OAuth 2 are supported, however the API instance on openstreetmap.org only supports OAuth 2 since June 2024. + * __Configure...__ Select the API instance, configure the URLs including read-only sources and authentication method. Basic Authentication, OAuth 1.0a and OAuth 2 are supported, however the API instance on openstreetmap.org only supports OAuth 2 since July 2024. * Data and Tasks layers: * __Info__ Display some information on the contents. * __Prune__ remove downloaded data from storage that is outside of the current screen and unmodified. @@ -169,6 +171,8 @@ To reposition or remove the "on-map" GPS button use the "Follow position button * **Pause GPX track** - pause recording the current GPX track * **Clear GPX track** - clear the current GPX track + + ### ![Transfer](../images/menu_transfer.png) Transfer Select either the transfer icon ![Transfer](../images/menu_transfer.png) or the "Transfer" menu item. This will display seven or eight options: @@ -183,7 +187,7 @@ Select either the transfer icon ![Transfer](../images/menu_transfer.png) or the * **Pan and zoom auto download** - download the area shown in the current screen automatically *(requires network connectivity)* * **Update data** - re-download data for all areas and update what is in memory *(requires network connectivity)* * **Clear data** - remove any OSM data in memory - * **File...** - saving and loading OSM data to/from on device files. + * **File...** - saving and loading OSM data to/from on device files. * **Export changes to OSC file** - write a ".osc" format file containing the current edits * **Apply changes from OSC file** - read a ".osc" format file and apply its contents * **Save to JOSM file...** - save as a JOSM compatible XML format file @@ -206,6 +210,8 @@ Select either the transfer icon ![Transfer](../images/menu_transfer.png) or the Show the user preference screens. The settings are split into two sets: the first screen contains the more commonly used preferences, the "Advanced preferences" contains the less used ones. + + ### ![Tools](../images/menu_tools.png) Tools… * **Apply stored offset to imagery** - apply stored offset, if it exists, for the current background layer diff --git a/documentation/docs/help/en/Uploading your changes.md b/documentation/docs/help/en/Uploading your changes.md new file mode 100644 index 0000000000..37e0707cbf --- /dev/null +++ b/documentation/docs/help/en/Uploading your changes.md @@ -0,0 +1,33 @@ +# Uploading your changes + +While Vespucci allows you to save your edits to local files on your device, most use cases center around uploading and publishing your data +to a server providing the [OpenStreetMap editing API](https://wiki.openstreetmap.org/wiki/API_v0.6) and that will be the instance available on [openstreetmap.org](https://openstreetmap.org) if you are contributing to regular OpenStreetMap. The following information only concerns itself with this standard configuration, other APIs and non-standard authorizations methods can be configured in the [data layer configuration](Main%20map%20display.md#layer_control). + +## Authorization + +To successfully publish your edits you need to authorize your Vespucci on your device to access your account on openstreetmap.prg on your behalf. You can start the authorization process by either selecting the corresponding option in the configuration dialog display on initial install of the app, by starting an upload process or by using the _Authorize OAuth_ option from the [Tools menu](Main%20map%20display.md#tools). + +_Note_ that you will need to have your OSM display name and password available and be connected to the Internet to successfully complete authorization, if you don't already have an account you will have to create one outside of Vespucci prior to starting authorization. While it technically would be possible to create the account from inside the app, doing so invokes additional requirements from google that we cannot fulfill for legal reasons. + +During the authorization process you will be connected to openstreetmap.org and asked to login to your account, you then need to confirm the authorization and will be returned back to Vespuccis map map display. + +If you have pending edits and for whatever reason cannot authorize, Vespucci reliably saves your edits on your device until you upload them or explicitly delete them. If you want to be extra safe you can [save them to a local file](Main%20map%20display.md#file) in OCS or (J)OSM format that you can read with Vespucci or import in to JOSM. + +## Uploading + +To upload your edits you can either select _Upload data to OSM server_ from the [Transfer menu](Main%20map%20display.md#trasfer), or select _Upload element_ from the overflow menu in any of the element selection modes or multi-select. The later will only upload the selected elements. + +The _Upload changes_ dialog display two tabs _Changes_ and _Properties_. The _Changes_ tab displays a list of all the OSM elements that will be affected by the upload, the type of change and an information button that allows you to view further information on the element and jump to it to make corrections. The _Properties_ tab allows you to set a _comment_ +for this upload and document that _source_ that was used to create the changes (for example _survey_ if you were personally present at the location in question). Prior entries in both +fields are retained and can be used when appropriate. + +It is considered best practice to provide meaningful information for other mappers in these fields. However Vespucci will automatically include a summary of your changes (starting with version 20.2) and a list of all imagery layers used for the current set of changes. If you leave the comment field empty it will include text pointing to the automatically generated summary. + +## Upload options + +- _Close changeset_ close the changeset after the upload. If this is unchecked, changesets will be left open and further uploads will be appended. Note that the changeset may be automatically closed by the API, this is detected on upload and a new changeset will be created. The default setting for this is enabled. +- _Close open changeset_ only displayed if an open changeset has been detected (that is _Close changeset_ must be disabled). Close the current changeset after upload. This setting only applies to the current upload. The default setting for this is disabled. +- _Request review_ add a tag asking for a review of the changes. Note this does not stop your changes from being immediately available after you have completed the upload, it is +simply an indication to other contributors and they might or might not actually review your edits. The default setting for this is disabled. +- _Warn if comment is empty_ As described above it is best practice to add a meaningful comment to your upload, if you don't want to do this, unchecking this option +will disable warnings that the comment is empty and disable the automatic switch to the _Properties_ tab prior to the upload. This is available from version 20.2 on, the default setting for this is enabled. \ No newline at end of file diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index c8f98e3fe1..a61769775f 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -63,6 +63,7 @@ nav: - 'Tag only mode': 'help/en/Tag only mode.md' - 'Indoor mode': 'help/en/Indoor mode.md' - 'Address mode': 'help/en/Address mode.md' + - 'Uploading your changes': 'help/en/Uploading your changes.md' - 'Conflict resolution': 'help/en/Conflict resolution.md' - 'Presets': 'help/en/Presets.md' - 'Aligning background imagery': 'help/en/Aligning background imagery.md' diff --git a/src/main/assets/help/en/Custom imagery.html b/src/main/assets/help/en/Custom imagery.html index a4b07065fc..f28e719169 100644 --- a/src/main/assets/help/en/Custom imagery.html +++ b/src/main/assets/help/en/Custom imagery.html @@ -12,7 +12,7 @@

Supported layer types

  • imagery layers in MBTiles and PMtiles V3 format on device
  • WMS servers supporting images in EPSG:3857 (including alternative names like EPSG:900913 etc) and EPSG:4326 projections
  • -

    Besides manually adding layers you can add WMS layers by querying a WMS server for a list of supported layers see layer control, or by querying the Open Aerial Map (OAM) service.

    +

    Besides manually adding layers you can add WMS layers by querying a WMS server for a list of supported layers see layer control, or by querying the Open Aerial Map (OAM) service.

    NOTE even though the URLs may look similar to those for a WMS layer, we do not support requesting data from ESRI MapServer with their native protocol.

    Adding a custom imagery source

    To add a custom layer goto the Preferences screen and select Custom imagery, press the + button to add a new layer, or you can select the same from the layer control, "+" menu. In the form you can set

    diff --git a/src/main/assets/help/en/Introduction.html b/src/main/assets/help/en/Introduction.html index 809ae56571..b417fb77cd 100644 --- a/src/main/assets/help/en/Introduction.html +++ b/src/main/assets/help/en/Introduction.html @@ -123,8 +123,8 @@

    Vespucci in "locked" mode

    Saving Your Changes

    (requires network connectivity)

    Select the same button or menu item you did for the download and now select "Upload data to OSM server".

    -

    Vespucci supports OAuth authorization and the classical username and password method. OAuth is preferable since it avoids sending passwords in the clear.

    -

    New Vespucci installs will have OAuth enabled by default. On your first attempt to upload modified data, a page from the OSM website loads. After you have logged on (over an encrypted connection) you will be asked to authorize Vespucci to edit using your account. If you want to or need to authorize the OAuth access to your account before editing there is a corresponding item in the "Tools" menu.

    +

    Vespucci supports OAuth 2, OAuth 1.0a authorization and the classical username and password method. Since July 1st 2024 the standard OpenStreetMap API only supports OAuth 2 and other methods are only available on private installations of the API or other projects that have repurposed OSM software.

    +

    Authorizing Vespucci to access your account on your behalf requires you to one time login with your display name and password. If your Vespucci install isn't authorized when you attempt to upload modified data you will be asked to login to the OSM website (over an encrypted connection). After you have logged on you will be asked to authorize Vespucci to edit using your account. If you want to or need to authorize the OAuth access to your account before editing there is a corresponding item in the "Tools" menu.

    If you want to save your work and do not have Internet access, you can save to a JOSM compatible .osm file and either upload later with Vespucci or with JOSM.

    Resolving conflicts on uploads

    Vespucci has a simple conflict resolver. However if you suspect that there are major issues with your edits, export your changes to a .osc file ("Export" menu item in the "Transfer" menu) and fix and upload them with JOSM. See the detailed help on conflict resolution.

    diff --git a/src/main/assets/help/en/Main map display.html b/src/main/assets/help/en/Main map display.html index 71a506b66a..9714a63a73 100644 --- a/src/main/assets/help/en/Main map display.html +++ b/src/main/assets/help/en/Main map display.html @@ -29,6 +29,7 @@

    Nearby point-of-interest display

    A nearby point-of-interest display can be shown by pulling the handle in the middle and top of the bottom menu bar up.

    The view will include a filtered view of all "POI"s displayed on the current map view. If no explicit filter is set this is limited to objects that have a key with one of shop, amenity, leisure, tourism, craft, office or emergency. If an explicit filter is set, that is a tag filter or a preset filter, or a mode (Indoor and C-mode) is selected that sets a filter the display will display objects that are allowed by the filter. For example if Indoor mode is selected, the display will only show POIs on the currently selected level.

    Tapping an entry in the display will center the map on the object and select it, tapping it a second time (just as on the map display) will start the property editor, in Tag only mode the property editor will start directly as expected. The POI entries are highlighted with the same validation indication as map icons, see validation styling.

    +

    Layer control

    Vespucci currently supports multiple tiled background imagery layers, multiple tiled overlay layers (both raster and Mapbox vector tiles), a grid/scale layer, a task layer, a photo layer, multiple GeoJSON layers, multiple GPX/GPS layers and, naturally, an OSM data layer. Tapping the layer control (upper right corner) will display the layer dialog).

    The layer dialog supports the following actions on the layer entries:

    @@ -100,7 +101,7 @@

    Layer control

  • Data layer:
      -
    • Configure... Select the API instance, configure the URLs including read-only sources and authentication method. Basic Authentication, OAuth 1.0a and OAuth 2 are supported, however the API instance on openstreetmap.org only supports OAuth 2 since June 2024.
    • +
    • Configure... Select the API instance, configure the URLs including read-only sources and authentication method. Basic Authentication, OAuth 1.0a and OAuth 2 are supported, however the API instance on openstreetmap.org only supports OAuth 2 since July 2024.
  • Data and Tasks layers: @@ -181,6 +182,7 @@

    Location Location

  • Pause GPX track - pause recording the current GPX track
  • Clear GPX track - clear the current GPX track
  • +

    Transfer Transfer

    Select either the transfer icon Transfer or the "Transfer" menu item. This will display seven or eight options:

      @@ -194,7 +196,7 @@

      Transfer Transfer

    • Pan and zoom auto download - download the area shown in the current screen automatically (requires network connectivity)
    • Update data - re-download data for all areas and update what is in memory (requires network connectivity)
    • Clear data - remove any OSM data in memory
    • -
    • File... - saving and loading OSM data to/from on device files. +
    • File... - saving and loading OSM data to/from on device files.
      • Export changes to OSC file - write a ".osc" format file containing the current edits
      • Apply changes from OSC file - read a ".osc" format file and apply its contents
      • @@ -224,6 +226,7 @@

        Transfer Transfer

      Preferences Preferences

      Show the user preference screens. The settings are split into two sets: the first screen contains the more commonly used preferences, the "Advanced preferences" contains the less used ones.

      +

      Tools Tools…

      • Apply stored offset to imagery - apply stored offset, if it exists, for the current background layer
      • diff --git a/src/main/assets/help/en/Uploading your changes.html b/src/main/assets/help/en/Uploading your changes.html new file mode 100644 index 0000000000..3112c0f2b2 --- /dev/null +++ b/src/main/assets/help/en/Uploading your changes.html @@ -0,0 +1,28 @@ + + + + + + + +

        Uploading your changes

        +

        While Vespucci allows you to save your edits to local files on your device, most use cases center around uploading and publishing your data to a server providing the OpenStreetMap editing API and that will be the instance available on openstreetmap.org if you are contributing to regular OpenStreetMap. The following information only concerns itself with this standard configuration, other APIs and non-standard authorizations methods can be configured in the data layer configuration.

        +

        Authorization

        +

        To successfully publish your edits you need to authorize your Vespucci on your device to access your account on openstreetmap.prg on your behalf. You can start the authorization process by either selecting the corresponding option in the configuration dialog display on initial install of the app, by starting an upload process or by using the Authorize OAuth option from the Tools menu.

        +

        Note that you will need to have your OSM display name and password available and be connected to the Internet to successfully complete authorization, if you don't already have an account you will have to create one outside of Vespucci prior to starting authorization. While it technically would be possible to create the account from inside the app, doing so invokes additional requirements from google that we cannot fulfill for legal reasons.

        +

        During the authorization process you will be connected to openstreetmap.org and asked to login to your account, you then need to confirm the authorization and will be returned back to Vespuccis map map display.

        +

        If you have pending edits and for whatever reason cannot authorize, Vespucci reliably saves your edits on your device until you upload them or explicitly delete them. If you want to be extra safe you can save them to a local file in OCS or (J)OSM format that you can read with Vespucci or import in to JOSM.

        +

        Uploading

        +

        To upload your edits you can either select Upload data to OSM server from the Transfer menu, or select Upload element from the overflow menu in any of the element selection modes or multi-select. The later will only upload the selected elements.

        +

        The Upload changes dialog display two tabs Changes and Properties. The Changes tab displays a list of all the OSM elements that will be affected by the upload, the type of change and an information button that allows you to view further information on the element and jump to it to make corrections. The Properties tab allows you to set a comment for this upload and document that source that was used to create the changes (for example survey if you were personally present at the location in question). Prior entries in both fields are retained and can be used when appropriate.

        +

        It is considered best practice to provide meaningful information for other mappers in these fields. However Vespucci will automatically include a summary of your changes (starting with version 20.2) and a list of all imagery layers used for the current set of changes. If you leave the comment field empty it will include text pointing to the automatically generated summary.

        +

        Upload options

        +
          +
        • Close changeset close the changeset after the upload. If this is unchecked, changesets will be left open and further uploads will be appended. Note that the changeset may be automatically closed by the API, this is detected on upload and a new changeset will be created. The default setting for this is enabled.
        • +
        • Close open changeset only displayed if an open changeset has been detected (that is Close changeset must be disabled). Close the current changeset after upload. This setting only applies to the current upload. The default setting for this is disabled.
        • +
        • Request review add a tag asking for a review of the changes. Note this does not stop your changes from being immediately available after you have completed the upload, it is simply an indication to other contributors and they might or might not actually review your edits. The default setting for this is disabled.
        • +
        • Warn if comment is empty As described above it is best practice to add a meaningful comment to your upload, if you don't want to do this, unchecking this option will disable warnings that the comment is empty and disable the automatic switch to the Properties tab prior to the upload. This is available from version 20.2 on, the default setting for this is enabled.
        • +
        + + + \ No newline at end of file diff --git a/src/main/java/de/blau/android/dialogs/ReviewAndUpload.java b/src/main/java/de/blau/android/dialogs/ReviewAndUpload.java index e5dfe8e129..88c009718f 100644 --- a/src/main/java/de/blau/android/dialogs/ReviewAndUpload.java +++ b/src/main/java/de/blau/android/dialogs/ReviewAndUpload.java @@ -1,7 +1,6 @@ package de.blau.android.dialogs; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import android.annotation.SuppressLint; @@ -20,7 +19,6 @@ import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.ImageButton; import android.widget.TextView; @@ -47,8 +45,6 @@ import de.blau.android.util.OnPageSelectedListener; import de.blau.android.util.ThemeUtils; import de.blau.android.util.Util; -import de.blau.android.validation.FormValidation; -import de.blau.android.validation.NotEmptyValidator; import de.blau.android.views.ExtendedViewPager; /** diff --git a/src/main/res/values/helptopics.xml b/src/main/res/values/helptopics.xml index 2fc2525598..dfb91b6b57 100644 --- a/src/main/res/values/helptopics.xml +++ b/src/main/res/values/helptopics.xml @@ -27,6 +27,7 @@ Advanced preferences Custom imagery Presets + Uploading your changes Conflict resolution GPS sources Keyboard @@ -82,6 +83,7 @@ @string/help_multiselect @string/help_longclick @string/help_pathcreation + @string/help_uploading @string/help_preferences @string/help_advanced_preferences @string/help_custom_imagery @@ -145,6 +147,7 @@ Multiselect Creating new objects Adding way nodes + Uploading your changes Preferences Advanced preferences Custom imagery