Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/App/Base.lproj/MainStoryboard.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@
<constraint firstItem="rdp-7O-8Ad" firstAttribute="top" secondItem="E22-sL-sNt" secondAttribute="bottom" constant="15" id="Knr-t7-Lp5"/>
<constraint firstItem="N5Q-nV-TPG" firstAttribute="top" secondItem="P70-Oz-du4" secondAttribute="top" constant="25" id="L7k-n3-wAc"/>
<constraint firstItem="UQ6-Pa-U58" firstAttribute="centerX" secondItem="53z-ij-akY" secondAttribute="centerX" id="LOe-DP-yFz"/>
<constraint firstItem="9t5-wq-j1u" firstAttribute="top" secondItem="LtX-Xt-YXq" secondAttribute="bottom" constant="12" id="MMF-rC-ZtS"/>
<constraint firstItem="9t5-wq-j1u" firstAttribute="top" secondItem="LtX-Xt-YXq" secondAttribute="bottom" constant="12" priority="750" id="MMF-rC-ZtS"/>
<constraint firstItem="Fo4-Nq-pAz" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="P70-Oz-du4" secondAttribute="leading" constant="16" id="OSS-Fj-87U"/>
<constraint firstItem="9O3-HG-HvC" firstAttribute="leading" secondItem="P70-Oz-du4" secondAttribute="leading" constant="12" id="Q13-4n-waC"/>
<constraint firstAttribute="trailing" secondItem="uDR-wZ-Dsb" secondAttribute="trailing" id="UgC-xC-hhs"/>
Expand Down
145 changes: 3 additions & 142 deletions src/App/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -26547,6 +26547,9 @@
}
}
},
"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" : {
Expand Down Expand Up @@ -64023,148 +64026,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" : {
Expand Down
1 change: 1 addition & 0 deletions src/App/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.bottomAnchor.constraint(equalTo: rulerView.topAnchor, constant: -12)

// Install gesture recognizers

Expand Down
42 changes: 39 additions & 3 deletions src/App/MessageDisplay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions src/Shared/CustomViews/HUDLabel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public final class HUDLabel: UIView {
let l = UILabel()
l.translatesAutoresizingMaskIntoConstraints = false
l.numberOfLines = 0
l.minimumScaleFactor = 0.5
return l
}()

Expand Down Expand Up @@ -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 }
Expand Down
7 changes: 4 additions & 3 deletions src/Shared/OSMModels/OsmMapData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1013,7 +1013,7 @@ final class OsmMapData: NSObject, NSSecureCoding {
imagery: String,
generator: String,
locale: String,
groupIds: Set<Int>) async throws
groupIds: Set<Int>) async throws -> Int64
{
consistencyCheck()

Expand Down Expand Up @@ -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.
Expand All @@ -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<OsmBaseObject>()) { $0.formUnion($1.objects) }
let groupIds = groups.reduce(into: Set<Int>()) { $0.formUnion($1.undoGroups) }
try await uploadChangeset(.objects(objects),
return try await uploadChangeset(.objects(objects),
comment: comment,
source: source,
imagery: imagery,
Expand Down
11 changes: 7 additions & 4 deletions src/Upload/UploadViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
8 changes: 5 additions & 3 deletions src/Upload/XmlEditorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
Loading