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

Commit 3c8c5d9

Browse files
authored
Update CertificateView.swift
1 parent 594c541 commit 3c8c5d9

File tree

1 file changed

+107
-76
lines changed

1 file changed

+107
-76
lines changed

Sources/prostore/views/CertificateView.swift

Lines changed: 107 additions & 76 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,86 +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
267285
}
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-
// 1) show cached immediately (if available)
276-
if let cached = CertChecker.cachedResult(p12Data: p12, mpData: mp, password: pw) {
277-
let status = (cached["certificate"] as? [String: String])?["status"]
278-
?? (cached["certificate_matching_status"] as? String)
279-
?? (cached["overall_status"] as? String)
280-
?? "Unknown"
281-
await MainActor.run {
282-
certStatuses[certCopy.folderName] = status + " (cached)"
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+
}
283301
}
284-
}
285302

286-
// 2) then fetch fresh and update (awaits network)
287-
do {
288-
let parsed = try await CertChecker.checkCert(
289-
mobileProvision: mp,
290-
mobileProvisionFilename: "profile.mobileprovision",
291-
p12: p12,
292-
p12Filename: "certificate.p12",
293-
password: pw
294-
)
295-
296-
let status = (parsed["certificate"] as? [String: String])?["status"]
297-
?? (parsed["certificate_matching_status"] as? String)
298-
?? (parsed["overall_status"] as? String)
299-
?? "Unknown"
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+
)
300312

301-
await MainActor.run {
302-
certStatuses[certCopy.folderName] = status
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+
}
303325
}
304326
} catch {
305327
await MainActor.run {
306328
certStatuses[certCopy.folderName] = "Check Error"
307329
}
308330
}
309-
}
331+
}
310332
}
311333
}
334+
335+
// MARK: - Helpers (moved out of reloadCertificatesAndEnsureSelection)
312336
private func loadExpiries() {
313337
for cert in customCertificates {
314338
let folderName = cert.folderName
315339
let certDir = CertificateFileManager.shared.certificatesDirectory.appendingPathComponent(folderName)
316340
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.
317344
let expiry = signer.getExpirationDate(provURL: provURL)
318345
certExpiries[folderName] = expiry
319346
}
320347
}
348+
321349
private func ensureSelection() {
322350
if selectedCert == nil || !customCertificates.contains(where: { $0.folderName == selectedCert }) {
323351
if let firstCert = customCertificates.first {
@@ -326,10 +354,11 @@ private func reloadCertificatesAndEnsureSelection() {
326354
}
327355
}
328356
}
357+
329358
private func deleteCertificate(_ cert: CustomCertificate) {
330359
try? CertificateFileManager.shared.deleteCertificate(folderName: cert.folderName)
331360
customCertificates = CertificateFileManager.shared.loadCertificates()
332-
361+
333362
if selectedCert == cert.folderName {
334363
if let newSelection = customCertificates.first {
335364
selectedCert = newSelection.folderName
@@ -343,6 +372,7 @@ private func reloadCertificatesAndEnsureSelection() {
343372
loadExpiries()
344373
}
345374
}
375+
346376
// MARK: - Add / Edit View
347377
struct AddCertificateView: View {
348378
@Environment(\.dismiss) private var dismiss
@@ -356,10 +386,12 @@ struct AddCertificateView: View {
356386
@State private var errorMessage = ""
357387
@State private var displayName: String = ""
358388
@State private var hasLoadedForEdit = false
389+
359390
init(editingCertificate: CustomCertificate? = nil, onSave: ((String) -> Void)? = nil) {
360391
self.editingCertificate = editingCertificate
361392
self.onSave = onSave
362393
}
394+
363395
var body: some View {
364396
NavigationStack {
365397
Form {
@@ -378,7 +410,7 @@ struct AddCertificateView: View {
378410
}
379411
}
380412
.disabled(isChecking)
381-
413+
382414
Button(action: { activeSheet = .prov }) {
383415
HStack {
384416
Image(systemName: "gearshape.fill")
@@ -394,20 +426,20 @@ struct AddCertificateView: View {
394426
}
395427
.disabled(isChecking)
396428
}
397-
429+
398430
Section(header: Text("Display Name")) {
399431
TextField("Optional Display Name", text: $displayName)
400432
.disabled(isChecking)
401433
}
402-
434+
403435
Section(header: Text("Password")) {
404436
SecureField("Enter Password", text: $password)
405437
.disabled(isChecking)
406438
Text("Enter the password for the certificate. Leave it blank if there is no password needed.")
407439
.font(.caption)
408440
.foregroundColor(.secondary)
409441
}
410-
442+
411443
if !errorMessage.isEmpty {
412444
Text(errorMessage)
413445
.foregroundColor(.red)
@@ -454,29 +486,31 @@ struct AddCertificateView: View {
454486
}
455487
}
456488
}
489+
457490
private func loadForEdit(cert: CustomCertificate) {
458491
let certFolder = CertificateFileManager.shared.certificatesDirectory.appendingPathComponent(cert.folderName)
459492
let p12URL = certFolder.appendingPathComponent("certificate.p12")
460493
let provURL = certFolder.appendingPathComponent("profile.mobileprovision")
461494
let passwordURL = certFolder.appendingPathComponent("password.txt")
462495
let nameURL = certFolder.appendingPathComponent("name.txt")
463-
496+
464497
p12File = CertificateFileItem(name: "certificate.p12", url: p12URL)
465498
provFile = CertificateFileItem(name: "profile.mobileprovision", url: provURL)
466-
499+
467500
if let pwData = try? Data(contentsOf: passwordURL), let pw = String(data: pwData, encoding: .utf8) {
468501
password = pw
469502
}
470503
if let nameData = try? Data(contentsOf: nameURL), let nameStr = String(data: nameData, encoding: .utf8) {
471504
displayName = nameStr
472505
}
473506
}
507+
474508
private func saveCertificate() {
475509
guard let p12URL = p12File?.url, let provURL = provFile?.url else { return }
476-
510+
477511
isChecking = true
478512
errorMessage = ""
479-
513+
480514
let workItem: DispatchWorkItem = DispatchWorkItem {
481515
do {
482516
var p12Data: Data
@@ -495,10 +529,10 @@ struct AddCertificateView: View {
495529
p12Data = try Data(contentsOf: p12URL)
496530
provData = try Data(contentsOf: provURL)
497531
}
498-
532+
499533
let checkResult = CertificatesManager.check(p12Data: p12Data, password: self.password, mobileProvisionData: provData)
500534
var dispatchError: String?
501-
535+
502536
switch checkResult {
503537
case .success(.success):
504538
if localDisplayName.isEmpty {
@@ -517,7 +551,7 @@ struct AddCertificateView: View {
517551
case .failure(let error):
518552
dispatchError = "Error: \(error.localizedDescription)"
519553
}
520-
554+
521555
DispatchQueue.main.async {
522556
self.isChecking = false
523557
if let err = dispatchError {
@@ -536,6 +570,3 @@ struct AddCertificateView: View {
536570
DispatchQueue.global(qos: .userInitiated).async(execute: workItem)
537571
}
538572
}
539-
540-
541-

0 commit comments

Comments
 (0)