Skip to content
This repository was archived by the owner on Mar 7, 2026. It is now read-only.

Commit 168bc1b

Browse files
authored
fix(ui): remove nested NavigationStacks and update navigation titles
1 parent 7f76def commit 168bc1b

4 files changed

Lines changed: 372 additions & 372 deletions

File tree

Sources/prosign/UIComponents.swift

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,181 @@ struct ActivityView: UIViewControllerRepresentable {
7878
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {}
7979
}
8080

81+
// MARK: - CertificateFileManager
82+
83+
class CertificateFileManager {
84+
static let shared = CertificateFileManager()
85+
let fileManager = FileManager.default
86+
let certificatesDirectory: URL
87+
88+
private init() {
89+
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
90+
certificatesDirectory = documentsDirectory.appendingPathComponent("certificates")
91+
createCertificatesDirectoryIfNeeded()
92+
}
93+
94+
private func createCertificatesDirectoryIfNeeded() {
95+
if !fileManager.fileExists(atPath: certificatesDirectory.path) {
96+
try? fileManager.createDirectory(at: certificatesDirectory, withIntermediateDirectories: true)
97+
}
98+
}
99+
100+
func loadCertificates() -> [CustomCertificate] {
101+
var resultCerts: [CustomCertificate] = []
102+
guard let folders = try? fileManager.contentsOfDirectory(at: certificatesDirectory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) else {
103+
return []
104+
}
105+
106+
for folder in folders {
107+
let nameURL = folder.appendingPathComponent("name.txt")
108+
if fileManager.fileExists(atPath: nameURL.path) {
109+
if let nameData = try? Data(contentsOf: nameURL),
110+
let nameString = String(data: nameData, encoding: .utf8) {
111+
resultCerts.append(CustomCertificate(displayName: nameString, folderName: folder.lastPathComponent))
112+
}
113+
} else {
114+
// Fallback display name if missing
115+
resultCerts.append(CustomCertificate(displayName: folder.lastPathComponent, folderName: folder.lastPathComponent))
116+
}
117+
}
118+
119+
return resultCerts
120+
}
121+
122+
func saveCertificate(p12Data: Data, provData: Data, password: String, displayName: String) throws -> String {
123+
let baseName = sanitizeFileName(displayName.isEmpty ? "Custom Certificate" : displayName)
124+
let p12HashNew = CertificatesManager.sha256Hex(p12Data)
125+
let provHashNew = CertificatesManager.sha256Hex(provData)
126+
let passwordHashNew = CertificatesManager.sha256Hex(password.data(using: .utf8) ?? Data())
127+
128+
// Check if identical cert already exists
129+
let existingFolders = try fileManager.contentsOfDirectory(at: certificatesDirectory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles])
130+
for folder in existingFolders {
131+
let p12URL = folder.appendingPathComponent("certificate.p12")
132+
let provURL = folder.appendingPathComponent("profile.mobileprovision")
133+
let passwordURL = folder.appendingPathComponent("password.txt")
134+
if fileManager.fileExists(atPath: p12URL.path) && fileManager.fileExists(atPath: provURL.path) && fileManager.fileExists(atPath: passwordURL.path) {
135+
do {
136+
let existingP12Data = try Data(contentsOf: p12URL)
137+
let existingProvData = try Data(contentsOf: provURL)
138+
let existingPasswordData = try Data(contentsOf: passwordURL)
139+
let existingPassword = String(data: existingPasswordData, encoding: .utf8) ?? ""
140+
141+
let p12HashExisting = CertificatesManager.sha256Hex(existingP12Data)
142+
let provHashExisting = CertificatesManager.sha256Hex(existingProvData)
143+
let passwordHashExisting = CertificatesManager.sha256Hex(existingPassword.data(using: .utf8) ?? Data())
144+
145+
if p12HashNew == p12HashExisting && provHashNew == provHashExisting && passwordHashNew == passwordHashExisting {
146+
throw NSError(domain: "CertificateFileManager", code: 2, userInfo: [NSLocalizedDescriptionKey: "This certificate already exists"])
147+
}
148+
} catch {
149+
// Skip if can't read existing
150+
continue
151+
}
152+
}
153+
}
154+
155+
// Create folder
156+
var finalName = baseName
157+
var counter = 1
158+
var folderURL = certificatesDirectory.appendingPathComponent(finalName)
159+
while fileManager.fileExists(atPath: folderURL.path) {
160+
counter += 1
161+
finalName = "\(baseName)-\(counter)"
162+
folderURL = certificatesDirectory.appendingPathComponent(finalName)
163+
}
164+
try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true)
165+
166+
try p12Data.write(to: folderURL.appendingPathComponent("certificate.p12"))
167+
try provData.write(to: folderURL.appendingPathComponent("profile.mobileprovision"))
168+
try password.data(using: .utf8)?.write(to: folderURL.appendingPathComponent("password.txt"))
169+
let displayToWrite = uniqueDisplayName(displayName, excludingFolder: nil)
170+
try displayToWrite.data(using: .utf8)?.write(to: folderURL.appendingPathComponent("name.txt"))
171+
172+
return finalName
173+
}
174+
175+
func updateCertificate(folderName: String, p12Data: Data, provData: Data, password: String, displayName: String) throws {
176+
let certificateFolder = certificatesDirectory.appendingPathComponent(folderName)
177+
let p12HashNew = CertificatesManager.sha256Hex(p12Data)
178+
let provHashNew = CertificatesManager.sha256Hex(provData)
179+
let passwordHashNew = CertificatesManager.sha256Hex(password.data(using: .utf8) ?? Data())
180+
181+
// Prevent accidental duplicate update matching another cert
182+
let existingFolders = try fileManager.contentsOfDirectory(at: certificatesDirectory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles])
183+
for folder in existingFolders where folder.lastPathComponent != folderName {
184+
let p12URL = folder.appendingPathComponent("certificate.p12")
185+
let provURL = folder.appendingPathComponent("profile.mobileprovision")
186+
let passwordURL = folder.appendingPathComponent("password.txt")
187+
if fileManager.fileExists(atPath: p12URL.path) && fileManager.fileExists(atPath: provURL.path) && fileManager.fileExists(atPath: passwordURL.path) {
188+
do {
189+
let existingP12Data = try Data(contentsOf: p12URL)
190+
let existingProvData = try Data(contentsOf: provURL)
191+
let existingPasswordData = try Data(contentsOf: passwordURL)
192+
let existingPassword = String(data: existingPasswordData, encoding: .utf8) ?? ""
193+
194+
let p12HashExisting = CertificatesManager.sha256Hex(existingP12Data)
195+
let provHashExisting = CertificatesManager.sha256Hex(existingProvData)
196+
let passwordHashExisting = CertificatesManager.sha256Hex(existingPassword.data(using: .utf8) ?? Data())
197+
198+
if p12HashNew == p12HashExisting && provHashNew == provHashExisting && passwordHashNew == passwordHashExisting {
199+
throw NSError(domain: "CertificateFileManager", code: 2, userInfo: [NSLocalizedDescriptionKey: "This updated certificate matches another existing one"])
200+
}
201+
} catch {
202+
// Skip if can't read existing
203+
continue
204+
}
205+
}
206+
}
207+
208+
// Overwrite files
209+
try p12Data.write(to: certificateFolder.appendingPathComponent("certificate.p12"))
210+
try provData.write(to: certificateFolder.appendingPathComponent("profile.mobileprovision"))
211+
try password.data(using: .utf8)?.write(to: certificateFolder.appendingPathComponent("password.txt"))
212+
let displayToWrite = uniqueDisplayName(displayName, excludingFolder: folderName)
213+
try displayToWrite.data(using: .utf8)?.write(to: certificateFolder.appendingPathComponent("name.txt"))
214+
}
215+
216+
func deleteCertificate(folderName: String) throws {
217+
let certificateFolder = certificatesDirectory.appendingPathComponent(folderName)
218+
try fileManager.removeItem(at: certificateFolder)
219+
}
220+
221+
private func sanitizeFileName(_ name: String) -> String {
222+
let invalidChars = CharacterSet(charactersIn: ":/\\?%*|\"<>")
223+
return name.components(separatedBy: invalidChars).joined(separator: "_")
224+
}
225+
226+
// Return a unique display name by appending " 2", " 3", ... if needed.
227+
// `excludingFolder` lets updateCertificate keep the current folder's name out of the conflict check.
228+
private func uniqueDisplayName(_ desired: String, excludingFolder: String? = nil) -> String {
229+
let base = desired.isEmpty ? "Custom Certificate" : desired
230+
var existingNames = Set<String>()
231+
if let folders = try? fileManager.contentsOfDirectory(at: certificatesDirectory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]) {
232+
for folder in folders {
233+
if folder.lastPathComponent == excludingFolder { continue }
234+
let nameURL = folder.appendingPathComponent("name.txt")
235+
if let data = try? Data(contentsOf: nameURL), let s = String(data: data, encoding: .utf8) {
236+
existingNames.insert(s)
237+
} else {
238+
// fallback to folder name if name.txt missing
239+
existingNames.insert(folder.lastPathComponent)
240+
}
241+
}
242+
}
243+
244+
if !existingNames.contains(base) {
245+
return base
246+
}
247+
248+
var counter = 2
249+
while existingNames.contains("\(base) \(counter)") {
250+
counter += 1
251+
}
252+
return "\(base) \(counter)"
253+
}
254+
}
255+
81256
// MARK: - PickerKind Enum
82257

83258
enum PickerKind: Identifiable {

Sources/prosign/views/AboutView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ struct AboutView: View {
9090
}
9191
}
9292
.listStyle(InsetGroupedListStyle())
93-
.navigationTitle("About")
93+
.navigationTitle("ProSign - About")
9494
}
9595
}
9696
}

0 commit comments

Comments
 (0)