From 836219bbd00b4619439d924221415736cca83f9c Mon Sep 17 00:00:00 2001 From: Kevin Ratzel Date: Wed, 21 Oct 2026 13:23:52 -0400 Subject: [PATCH 01/10] Add success message for changeset upload --- src/App/Localizable.xcstrings | 144 +--------------------------------- 1 file changed, 2 insertions(+), 142 deletions(-) diff --git a/src/App/Localizable.xcstrings b/src/App/Localizable.xcstrings index 8a7b319fd..2898964a2 100644 --- a/src/App/Localizable.xcstrings +++ b/src/App/Localizable.xcstrings @@ -26540,6 +26540,8 @@ } } }, + "Changes saved in changeset %@" : { + "comment" : "A message that appears in a banner at the top of the screen after a set of changes has been uploaded to the OpenStreetMap server. The argument is the changeset ID." "Changeset" : { "comment" : "OSM changeset identifier", "localizations" : { @@ -63963,148 +63965,6 @@ } } }, - "Upload complete!" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "اكتمل رفع البيانات!" - } - }, - "ars" : { - "stringUnit" : { - "state" : "translated", - "value" : "اكتمل رفع البيانات!" - } - }, - "cs" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nahrávání dokončeno" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hochladen abgeschlossen!" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Se completó la carga." - } - }, - "et" : { - "stringUnit" : { - "state" : "translated", - "value" : "Üleslaadimine lõpetatud!" - } - }, - "eu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Igorria!" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Téléversement effectué !" - } - }, - "hr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Prijenos gotov!" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pengunggahan selesai!" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "アップロード完了!" - } - }, - "nb-NO" : { - "stringUnit" : { - "state" : "translated", - "value" : "Opplasting fullført!" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Uploaden beëindigd!" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "Envio terminado!" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Envio completo!" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Загрузка завершена!" - } - }, - "ta" : { - "stringUnit" : { - "state" : "translated", - "value" : "பதிவேற்றம் முடிந்தது!" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Yükleme tamamlandı!" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Завантаження завершено!" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đã tải lên xong!" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "上传完成!" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "上傳完畢!" - } - }, - "zh-Hant-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "上傳完畢!" - } - } - } - }, "Upload succeeded but the server returned mismatched data.\n\nYou should use Clear Cache > OSM Data before continuing to map." : { "comment" : "'Clear Cache' and 'OSM Data' should match what your language uses for the same terms in Display settings", "localizations" : { From 54bba1798b9921ad7c4267350e4c9f6abc77c056 Mon Sep 17 00:00:00 2001 From: Kevin Ratzel Date: Wed, 21 Oct 2026 13:26:18 -0400 Subject: [PATCH 02/10] Fix constraint setup for flash link in MainViewController --- src/App/MainViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/src/App/MainViewController.swift b/src/App/MainViewController.swift index 55d20ca65..74acdded3 100644 --- a/src/App/MainViewController.swift +++ b/src/App/MainViewController.swift @@ -338,6 +338,7 @@ final class MainViewController: UIViewController, DPadDelegate, // tell our error display manager where to display messages MessageDisplay.shared.topViewController = self MessageDisplay.shared.flashLabel = flashLabel + MessageDisplay.shared.flashLinkConstraint = flashLabel.bottonAnchor.constraint(equalTo: rulerView.topAnchor, constant: -12) // Install gesture recognizers From 18b8832fc957ce362b2850d0ae69293da4654e99 Mon Sep 17 00:00:00 2001 From: Kevin Ratzel Date: Wed, 21 Oct 2026 13:29:35 -0400 Subject: [PATCH 03/10] Enhance flashMessage to include optional link Added support for displaying a link in the flash message. --- src/App/MessageDisplay.swift | 42 +++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/App/MessageDisplay.swift b/src/App/MessageDisplay.swift index 19304e776..82ab1745e 100644 --- a/src/App/MessageDisplay.swift +++ b/src/App/MessageDisplay.swift @@ -6,9 +6,10 @@ // Copyright © 2026 Bryce Cogswell. All rights reserved. // +import SafariServices import UIKit -class MessageDisplay { +class MessageDisplay: NSObject { static let shared = MessageDisplay() weak var flashLabel: HUDLabel! { @@ -20,14 +21,22 @@ class MessageDisplay { flashLabel.backgroundColor = .black.withAlphaComponent(0.7) flashLabel.textColor = .white flashLabel.contentInsets = .init(top: 4, left: 12, bottom: 4, right: 12) + flashLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(flashTapped))) + flashLabel.layer.cornerCurve = .continuous + flashBlur.frame = flashLabel.bounds + flashBlur.autoresizingMask = [.flexibleWidth, .flexibleHeight] + flashLabel.insertSubview(flashBlur, at: 0) } } weak var topViewController: UIViewController? + var flashLinkConstraint: NSLayoutConstraint? var lastErrorDate: Date? // to prevent spamming of error dialogs var ignoreNetworkErrorsUntilDate: Date? private var flashGeneration = 0 + private var flashLink: URL? + private lazy var flashBlur = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial)) func showAlert(_ alert: UIAlertController) { topViewController?.present(alert, animated: true) @@ -105,8 +114,35 @@ class MessageDisplay { } } - func flashMessage(title: String?, message: String, duration: TimeInterval = 1.0) { - flashLabel.text = title.map { $0 + "\n\n" + message } ?? message + @objc private func flashTapped() { + flashLink.map { topViewController?.present(SFSafariViewController(url: $0), animated: true) } + } + + func flashMessage(title: String?, message: String, duration: TimeInterval = 1.0, link: URL? = nil) { + flashLink = link + let text = title.map { $0 + "\n\n" + message } ?? message + let font = UIFont.preferredFont(forTextStyle: link == nil ? .title3 : .headline) + let attributed = NSMutableAttributedString(string: link == nil ? text : " \(text) ", attributes: [ + .font: font, + .foregroundColor: link == nil ? UIColor.white : UIColor.label + ]) + if link != nil { + func symbol(_ name: String, _ color: UIColor) -> NSAttributedString { + let image = UIImage(systemName: name, withConfiguration: UIImage.SymbolConfiguration(font: font)) + return NSAttributedString(attachment: NSTextAttachment(image: image!.withTintColor(color, renderingMode: .alwaysOriginal))) + } + attributed.insert(symbol("checkmark.circle.fill", .systemGreen), at: 0) + attributed.append(symbol("chevron.right", .tertiaryLabel)) + } + flashBlur.isHidden = link == nil + flashLinkConstraint?.isActive = link != nil + flashLabel.numberOfLines = link == nil ? 0 : 1 + flashLabel.adjustsFontSizeToFitWidth = link != nil + flashLabel.backgroundColor = link == nil ? .black.withAlphaComponent(0.7) : .clear + flashLabel.layer.cornerRadius = link == nil ? 5 : 16 + flashLabel.contentInsets = link == nil ? .init(top: 4, left: 12, bottom: 4, right: 12) + : .init(top: 12, left: 16, bottom: 12, right: 16) + flashLabel.attributedText = attributed flashLabel.superview?.bringSubviewToFront(flashLabel) if flashLabel.isHidden { From 3a3c99677f1a588450f45083236ef38c9607843d Mon Sep 17 00:00:00 2001 From: Kevin Ratzel Date: Wed, 21 Oct 2026 13:30:46 -0400 Subject: [PATCH 04/10] Add priority to a constraint in MainStoryboard Added priority to a constraint in MainStoryboard. --- src/App/Base.lproj/MainStoryboard.storyboard | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App/Base.lproj/MainStoryboard.storyboard b/src/App/Base.lproj/MainStoryboard.storyboard index 39d636453..8840c3705 100644 --- a/src/App/Base.lproj/MainStoryboard.storyboard +++ b/src/App/Base.lproj/MainStoryboard.storyboard @@ -321,7 +321,7 @@ - + From 0f54808b28e69afb52e975b5bbf293e9fdc985d6 Mon Sep 17 00:00:00 2001 From: Kevin Ratzel Date: Wed, 21 Oct 2026 13:32:22 -0400 Subject: [PATCH 05/10] Enhance HUDLabel with font scaling options Added minimumScaleFactor and adjustsFontSizeToFitWidth properties to HUDLabel. --- src/Shared/CustomViews/HUDLabel.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Shared/CustomViews/HUDLabel.swift b/src/Shared/CustomViews/HUDLabel.swift index a31e66b08..e97971317 100644 --- a/src/Shared/CustomViews/HUDLabel.swift +++ b/src/Shared/CustomViews/HUDLabel.swift @@ -15,6 +15,7 @@ public final class HUDLabel: UIView { let l = UILabel() l.translatesAutoresizingMaskIntoConstraints = false l.numberOfLines = 0 + l.minimumScaleFactor = 0.5 return l }() @@ -58,6 +59,11 @@ public final class HUDLabel: UIView { set { label.numberOfLines = newValue } } + public var adjustsFontSizeToFitWidth: Bool { + get { label.adjustsFontSizeToFitWidth } + set { label.adjustsFontSizeToFitWidth = newValue } + } + public var adjustsFontForContentSizeCategory: Bool { get { label.adjustsFontForContentSizeCategory } set { label.adjustsFontForContentSizeCategory = newValue } From 862ff2a6309677a3bf8b8e706581f9976da2ae9a Mon Sep 17 00:00:00 2001 From: Kevin Ratzel Date: Wed, 21 Oct 2026 13:34:32 -0400 Subject: [PATCH 06/10] Update async function signatures to return Int64 --- src/Shared/OSMModels/OsmMapData.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Shared/OSMModels/OsmMapData.swift b/src/Shared/OSMModels/OsmMapData.swift index 44dd90828..8c18d3f4b 100644 --- a/src/Shared/OSMModels/OsmMapData.swift +++ b/src/Shared/OSMModels/OsmMapData.swift @@ -1013,7 +1013,7 @@ final class OsmMapData: NSObject, NSSecureCoding { imagery: String, generator: String, locale: String, - groupIds: Set) async throws + groupIds: Set) async throws -> Int64 { consistencyCheck() @@ -1064,6 +1064,7 @@ final class OsmMapData: NSObject, NSSecureCoding { // The upload is complete and our state is consistent, but tell the user to refresh. throw Error.serverSkippedDelete } + return changesetID } /// Upload xml generated by mapData. @@ -1085,11 +1086,11 @@ final class OsmMapData: NSObject, NSSecureCoding { source: String, imagery: String, generator: String, - locale: String) async throws + locale: String) async throws -> Int64 { let objects = groups.reduce(into: Set()) { $0.formUnion($1.objects) } let groupIds = groups.reduce(into: Set()) { $0.formUnion($1.undoGroups) } - try await uploadChangeset(.objects(objects), + return try await uploadChangeset(.objects(objects), comment: comment, source: source, imagery: imagery, From ee79e1f1194f7a0919c69fc90cbf2b44435edd65 Mon Sep 17 00:00:00 2001 From: Kevin Ratzel Date: Wed, 21 Oct 2026 13:36:10 -0400 Subject: [PATCH 07/10] Update upload success message to include changeset ID --- src/Upload/UploadViewController.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Upload/UploadViewController.swift b/src/Upload/UploadViewController.swift index b6c6c750b..bff36e107 100644 --- a/src/Upload/UploadViewController.swift +++ b/src/Upload/UploadViewController.swift @@ -805,7 +805,7 @@ class UploadViewController: UIViewController { let imagery = currentImagery() do { - try await mapData.uploadChangeset(for: selectedGroups, + let changesetID = try await mapData.uploadChangeset(for: selectedGroups, comment: comment, source: source, imagery: imagery, @@ -814,9 +814,12 @@ class UploadViewController: UIViewController { dismiss(animated: true) MainActor.runAfter(nanoseconds: 300_000000) { appDelegate.mapView.setNeedsLayout() - MessageDisplay.shared.flashMessage(title: nil, - message: NSLocalizedString("Upload complete!", comment: ""), - duration: 1.5) + MessageDisplay.shared.flashMessage( + title: nil, + message: String.localizedStringWithFormat(NSLocalizedString("Changes saved in changeset %@", comment: ""), + String(changesetID)), + duration: 4.0, + link: OSM_SERVER.serverURL.appendingPathComponent("changeset/\(changesetID)")) var editCount = UserPrefs.shared.uploadCountPerVersion.value ?? 0 editCount += 1 UserPrefs.shared.uploadCountPerVersion.value = editCount From 51074555df32abc19c687189374d9dee9650584b Mon Sep 17 00:00:00 2001 From: Kevin Ratzel Date: Wed, 21 Oct 2026 13:37:03 -0400 Subject: [PATCH 08/10] Modify upload completion message format Update upload completion message to include changeset ID. --- src/Upload/XmlEditorViewController.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Upload/XmlEditorViewController.swift b/src/Upload/XmlEditorViewController.swift index 69b13dde6..803cdcadd 100644 --- a/src/Upload/XmlEditorViewController.swift +++ b/src/Upload/XmlEditorViewController.swift @@ -82,7 +82,7 @@ final class XmlEditorViewController: UIViewController { do { let xmlDoc = try DDXMLDocument(xmlString: textView.text ?? "", options: 0) - try await mapData.uploadChangeset(.xml(xmlDoc), + let changesetID = try await mapData.uploadChangeset(.xml(xmlDoc), comment: comment, source: source, imagery: imagery, @@ -95,8 +95,10 @@ final class XmlEditorViewController: UIViewController { appDelegate.mapView.setNeedsLayout() MessageDisplay.shared.flashMessage( title: nil, - message: NSLocalizedString("Upload complete!", comment: ""), - duration: 1.5) + message: String.localizedStringWithFormat(NSLocalizedString("Changes saved in changeset %@", comment: ""), + String(changesetID)), + duration: 4.0, + link: OSM_SERVER.serverURL.appendingPathComponent("changeset/\(changesetID)")) var editCount = UserPrefs.shared.uploadCountPerVersion.value ?? 0 editCount += 1 UserPrefs.shared.uploadCountPerVersion.value = editCount From c23390103985d2a8e6e8416fab711081e21830e9 Mon Sep 17 00:00:00 2001 From: Kevin Ratzel Date: Thu, 22 Oct 2026 09:29:33 -0400 Subject: [PATCH 09/10] Fix typo in flashLinkConstraint assignment --- src/App/MainViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App/MainViewController.swift b/src/App/MainViewController.swift index 74acdded3..a0e3abe98 100644 --- a/src/App/MainViewController.swift +++ b/src/App/MainViewController.swift @@ -338,7 +338,7 @@ final class MainViewController: UIViewController, DPadDelegate, // tell our error display manager where to display messages MessageDisplay.shared.topViewController = self MessageDisplay.shared.flashLabel = flashLabel - MessageDisplay.shared.flashLinkConstraint = flashLabel.bottonAnchor.constraint(equalTo: rulerView.topAnchor, constant: -12) + MessageDisplay.shared.flashLinkConstraint = flashLabel.bottomAnchor.constraint(equalTo: rulerView.topAnchor, constant: -12) // Install gesture recognizers From dbae061cc80426fc8a2463726f71b7d233040d6f Mon Sep 17 00:00:00 2001 From: Kevin Ratzel Date: Thu, 22 Oct 2026 11:42:37 -0400 Subject: [PATCH 10/10] Update localization strings in Localizable.xcstrings --- src/App/Localizable.xcstrings | 1 + 1 file changed, 1 insertion(+) diff --git a/src/App/Localizable.xcstrings b/src/App/Localizable.xcstrings index 0f1e1636b..f58c3a7ad 100644 --- a/src/App/Localizable.xcstrings +++ b/src/App/Localizable.xcstrings @@ -26549,6 +26549,7 @@ }, "Changes saved in changeset %@" : { "comment" : "A message that appears in a banner at the top of the screen after a set of changes has been uploaded to the OpenStreetMap server. The argument is the changeset ID." + }, "Changeset" : { "comment" : "OSM changeset identifier", "localizations" : {