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

Commit 9750f58

Browse files
authored
Refactor CertificateView and AddCertificateView
1 parent b74314a commit 9750f58

File tree

1 file changed

+116
-69
lines changed

1 file changed

+116
-69
lines changed

Sources/prostore/views/CertificateView.swift

Lines changed: 116 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import SwiftUI
22
import UniformTypeIdentifiers
3+
34
// Centralized types to avoid conflicts
45
struct CertificateFileItem {
56
var name: String = ""
@@ -10,6 +11,7 @@ struct CustomCertificate: Identifiable {
1011
let displayName: String
1112
let folderName: String
1213
}
14+
1315
// MARK: - Date Extension for Formatting
1416
extension Date {
1517
func formattedWithOrdinal() -> String {
@@ -39,8 +41,10 @@ extension Date {
3941
return "\(number)\(suffix)"
4042
}
4143
}
44+
4245
// MARK: - CertificateView (List + Add/Edit launchers)
4346
struct CertificateView: View {
47+
// MARK: - State
4448
@State private var customCertificates: [CustomCertificate] = []
4549
@State private var certExpiries: [String: Date?] = [:]
4650
@State private var certStatuses: [String: String] = [:]
@@ -51,6 +55,8 @@ struct CertificateView: View {
5155
@State private var showingDeleteAlert = false
5256
@State private var certToDelete: CustomCertificate?
5357
@State private var newlyAddedFolder: String? = nil
58+
59+
// MARK: - Body
5460
var body: some View {
5561
ScrollView {
5662
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 20) {
@@ -117,6 +123,8 @@ struct CertificateView: View {
117123
reloadCertificatesAndEnsureSelection()
118124
}
119125
}
126+
127+
// MARK: - UI helpers
120128
private func certificateItem(for cert: CustomCertificate) -> some View {
121129
ZStack(alignment: .top) {
122130
certificateContent(for: cert)
@@ -135,6 +143,7 @@ struct CertificateView: View {
135143
certificateButtons(for: cert)
136144
}
137145
}
146+
138147
private func certificateContent(for cert: CustomCertificate) -> some View {
139148
VStack(alignment: .center, spacing: 12) {
140149
Text(cert.displayName)
@@ -167,6 +176,7 @@ struct CertificateView: View {
167176
.stroke(selectedCert == cert.folderName ? Color.blue : Color.clear, lineWidth: 3)
168177
)
169178
}
179+
170180
private func expiryDisplay(for expiry: Date) -> some View {
171181
let now = Date()
172182
let components = Calendar.current.dateComponents([.day], from: now, to: expiry)
@@ -184,6 +194,7 @@ struct CertificateView: View {
184194
.fontWeight(.medium)
185195
.foregroundColor(.primary)
186196
}
197+
187198
private func certificateBackground(for cert: CustomCertificate) -> Color {
188199
let status = certStatuses[cert.folderName] ?? "Unknown"
189200
if status == "Revoked" {
@@ -204,6 +215,7 @@ struct CertificateView: View {
204215
return .green.opacity(0.15)
205216
}
206217
}
218+
207219
private func certificateButtons(for cert: CustomCertificate) -> some View {
208220
HStack {
209221
Button(action: {
@@ -217,9 +229,9 @@ struct CertificateView: View {
217229
.background(Color(.systemGray6).opacity(0.8))
218230
.clipShape(Circle())
219231
}
220-
232+
221233
Spacer()
222-
234+
223235
Button(action: {
224236
if customCertificates.count > 1 {
225237
certToDelete = cert
@@ -238,72 +250,102 @@ struct CertificateView: View {
238250
.padding(.top, 12)
239251
.padding(.horizontal, 12)
240252
}
241-
private func reloadCertificatesAndEnsureSelection() {
242-
customCertificates = CertificateFileManager.shared.loadCertificates()
243-
244-
selectedCert = UserDefaults.standard.string(forKey: "selectedCertificateFolder")
245-
ensureSelection()
246-
loadExpiries()
247-
248-
for cert in customCertificates {
249-
let certCopy = cert
250-
Task<Void, Never> {
251-
do {
252-
let dir = CertificateFileManager.shared.certificatesDirectory.appendingPathComponent(certCopy.folderName)
253-
254-
// Check if files exist
255-
let p12URL = dir.appendingPathComponent("certificate.p12")
256-
let mpURL = dir.appendingPathComponent("profile.mobileprovision")
257-
let pwURL = dir.appendingPathComponent("password.txt")
258-
259-
let fileManager = FileManager.default
260-
let p12Exists = fileManager.fileExists(atPath: p12URL.path)
261-
let mpExists = fileManager.fileExists(atPath: mpURL.path)
262-
let pwExists = fileManager.fileExists(atPath: pwURL.path)
263-
264-
if !p12Exists || !mpExists || !pwExists {
265-
await MainActor.run {
266-
certStatuses[certCopy.folderName] = "Missing Files"
253+
254+
// MARK: - Certificate loading & status
255+
private func reloadCertificatesAndEnsureSelection() {
256+
customCertificates = CertificateFileManager.shared.loadCertificates()
257+
258+
selectedCert = UserDefaults.standard.string(forKey: "selectedCertificateFolder")
259+
ensureSelection()
260+
loadExpiries()
261+
262+
for cert in customCertificates {
263+
let certCopy = cert
264+
265+
Task {
266+
do {
267+
let dir = CertificateFileManager.shared.certificatesDirectory
268+
.appendingPathComponent(certCopy.folderName)
269+
270+
// Check if files exist
271+
let p12URL = dir.appendingPathComponent("certificate.p12")
272+
let mpURL = dir.appendingPathComponent("profile.mobileprovision")
273+
let pwURL = dir.appendingPathComponent("password.txt")
274+
275+
let fileManager = FileManager.default
276+
let p12Exists = fileManager.fileExists(atPath: p12URL.path)
277+
let mpExists = fileManager.fileExists(atPath: mpURL.path)
278+
let pwExists = fileManager.fileExists(atPath: pwURL.path)
279+
280+
if !p12Exists || !mpExists || !pwExists {
281+
await MainActor.run {
282+
certStatuses[certCopy.folderName] = "Missing Files"
283+
}
284+
return
285+
}
286+
287+
// Read files (safe inside Task)
288+
let p12 = try Data(contentsOf: p12URL)
289+
let mp = try Data(contentsOf: mpURL)
290+
let pw = (try? String(contentsOf: pwURL, encoding: .utf8))?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
291+
292+
// 1) show cached immediately (if available)
293+
if let cached = CertChecker.cachedResult(p12Data: p12, mpData: mp, password: pw) {
294+
let status = (cached["certificate"] as? [String: String])?["status"]
295+
?? (cached["certificate_matching_status"] as? String)
296+
?? (cached["overall_status"] as? String)
297+
?? "Unknown"
298+
await MainActor.run {
299+
certStatuses[certCopy.folderName] = status + " (cached)"
300+
}
301+
}
302+
303+
// 2) then fetch fresh and update (awaits network)
304+
do {
305+
let parsed = try await CertChecker.checkCert(
306+
mobileProvision: mp,
307+
mobileProvisionFilename: "profile.mobileprovision",
308+
p12: p12,
309+
p12Filename: "certificate.p12",
310+
password: pw
311+
)
312+
313+
let status = (parsed["certificate"] as? [String: String])?["status"]
314+
?? (parsed["certificate_matching_status"] as? String)
315+
?? (parsed["overall_status"] as? String)
316+
?? "Unknown"
317+
318+
await MainActor.run {
319+
certStatuses[certCopy.folderName] = status
320+
}
321+
} catch {
322+
await MainActor.run {
323+
certStatuses[certCopy.folderName] = "Check Error"
324+
}
325+
}
326+
} catch {
327+
await MainActor.run {
328+
certStatuses[certCopy.folderName] = "Check Error"
267329
}
268-
return
269-
}
270-
271-
let p12 = try Data(contentsOf: p12URL)
272-
let mp = try Data(contentsOf: mpURL)
273-
let pw = (try? String(contentsOf: pwURL, encoding: .utf8))?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
274-
275-
let parsed = try await CertChecker.checkCert(
276-
mobileProvision: mp,
277-
mobileProvisionFilename: "profile.mobileprovision",
278-
p12: p12,
279-
p12Filename: "certificate.p12",
280-
password: pw
281-
)
282-
283-
let status = (parsed["certificate"] as? [String: String])?["status"] ??
284-
(parsed["certificate_matching_status"] as? String) ??
285-
"Unknown"
286-
287-
await MainActor.run {
288-
certStatuses[certCopy.folderName] = status
289330
}
290-
} catch {
291-
await MainActor.run {
292-
certStatuses[certCopy.folderName] = "Check Error"
293-
}
294-
}
295-
}
331+
}
332+
}
296333
}
297-
}
334+
335+
// MARK: - Helpers (moved out of reloadCertificatesAndEnsureSelection)
298336
private func loadExpiries() {
299337
for cert in customCertificates {
300338
let folderName = cert.folderName
301339
let certDir = CertificateFileManager.shared.certificatesDirectory.appendingPathComponent(folderName)
302340
let provURL = certDir.appendingPathComponent("profile.mobileprovision")
341+
342+
// signer.getExpirationDate(provURL:) is presumed synchronous in your project.
343+
// If it's async change this to Task + await accordingly.
303344
let expiry = signer.getExpirationDate(provURL: provURL)
304345
certExpiries[folderName] = expiry
305346
}
306347
}
348+
307349
private func ensureSelection() {
308350
if selectedCert == nil || !customCertificates.contains(where: { $0.folderName == selectedCert }) {
309351
if let firstCert = customCertificates.first {
@@ -312,10 +354,11 @@ private func reloadCertificatesAndEnsureSelection() {
312354
}
313355
}
314356
}
357+
315358
private func deleteCertificate(_ cert: CustomCertificate) {
316359
try? CertificateFileManager.shared.deleteCertificate(folderName: cert.folderName)
317360
customCertificates = CertificateFileManager.shared.loadCertificates()
318-
361+
319362
if selectedCert == cert.folderName {
320363
if let newSelection = customCertificates.first {
321364
selectedCert = newSelection.folderName
@@ -329,6 +372,7 @@ private func reloadCertificatesAndEnsureSelection() {
329372
loadExpiries()
330373
}
331374
}
375+
332376
// MARK: - Add / Edit View
333377
struct AddCertificateView: View {
334378
@Environment(\.dismiss) private var dismiss
@@ -342,10 +386,12 @@ struct AddCertificateView: View {
342386
@State private var errorMessage = ""
343387
@State private var displayName: String = ""
344388
@State private var hasLoadedForEdit = false
389+
345390
init(editingCertificate: CustomCertificate? = nil, onSave: ((String) -> Void)? = nil) {
346391
self.editingCertificate = editingCertificate
347392
self.onSave = onSave
348393
}
394+
349395
var body: some View {
350396
NavigationStack {
351397
Form {
@@ -364,7 +410,7 @@ struct AddCertificateView: View {
364410
}
365411
}
366412
.disabled(isChecking)
367-
413+
368414
Button(action: { activeSheet = .prov }) {
369415
HStack {
370416
Image(systemName: "gearshape.fill")
@@ -380,20 +426,20 @@ struct AddCertificateView: View {
380426
}
381427
.disabled(isChecking)
382428
}
383-
429+
384430
Section(header: Text("Display Name")) {
385431
TextField("Optional Display Name", text: $displayName)
386432
.disabled(isChecking)
387433
}
388-
434+
389435
Section(header: Text("Password")) {
390436
SecureField("Enter Password", text: $password)
391437
.disabled(isChecking)
392438
Text("Enter the password for the certificate. Leave it blank if there is no password needed.")
393439
.font(.caption)
394440
.foregroundColor(.secondary)
395441
}
396-
442+
397443
if !errorMessage.isEmpty {
398444
Text(errorMessage)
399445
.foregroundColor(.red)
@@ -440,29 +486,31 @@ struct AddCertificateView: View {
440486
}
441487
}
442488
}
489+
443490
private func loadForEdit(cert: CustomCertificate) {
444491
let certFolder = CertificateFileManager.shared.certificatesDirectory.appendingPathComponent(cert.folderName)
445492
let p12URL = certFolder.appendingPathComponent("certificate.p12")
446493
let provURL = certFolder.appendingPathComponent("profile.mobileprovision")
447494
let passwordURL = certFolder.appendingPathComponent("password.txt")
448495
let nameURL = certFolder.appendingPathComponent("name.txt")
449-
496+
450497
p12File = CertificateFileItem(name: "certificate.p12", url: p12URL)
451498
provFile = CertificateFileItem(name: "profile.mobileprovision", url: provURL)
452-
499+
453500
if let pwData = try? Data(contentsOf: passwordURL), let pw = String(data: pwData, encoding: .utf8) {
454501
password = pw
455502
}
456503
if let nameData = try? Data(contentsOf: nameURL), let nameStr = String(data: nameData, encoding: .utf8) {
457504
displayName = nameStr
458505
}
459506
}
507+
460508
private func saveCertificate() {
461509
guard let p12URL = p12File?.url, let provURL = provFile?.url else { return }
462-
510+
463511
isChecking = true
464512
errorMessage = ""
465-
513+
466514
let workItem: DispatchWorkItem = DispatchWorkItem {
467515
do {
468516
var p12Data: Data
@@ -481,10 +529,10 @@ struct AddCertificateView: View {
481529
p12Data = try Data(contentsOf: p12URL)
482530
provData = try Data(contentsOf: provURL)
483531
}
484-
532+
485533
let checkResult = CertificatesManager.check(p12Data: p12Data, password: self.password, mobileProvisionData: provData)
486534
var dispatchError: String?
487-
535+
488536
switch checkResult {
489537
case .success(.success):
490538
if localDisplayName.isEmpty {
@@ -503,7 +551,7 @@ struct AddCertificateView: View {
503551
case .failure(let error):
504552
dispatchError = "Error: \(error.localizedDescription)"
505553
}
506-
554+
507555
DispatchQueue.main.async {
508556
self.isChecking = false
509557
if let err = dispatchError {
@@ -522,4 +570,3 @@ struct AddCertificateView: View {
522570
DispatchQueue.global(qos: .userInitiated).async(execute: workItem)
523571
}
524572
}
525-

0 commit comments

Comments
 (0)