1- // CertificateView.swift
21import SwiftUI
32import UniformTypeIdentifiers
3+ import ProStoreTools
44// Centralized types to avoid conflicts
55struct CertificateFileItem {
66 var name : String = " "
@@ -11,16 +11,52 @@ struct CustomCertificate: Identifiable {
1111 let displayName : String
1212 let folderName : String
1313}
14+
15+ enum CertStatus {
16+ case loading
17+ case signed( Date ? )
18+ case revoked( Date ? )
19+ case unknown
20+ }
21+
22+ extension CertStatus {
23+ var description : String {
24+ switch self {
25+ case . loading:
26+ return " Status: Loading "
27+ case . signed:
28+ return " Status: Signed "
29+ case . revoked:
30+ return " Status: Revoked "
31+ case . unknown:
32+ return " Status: Unknown "
33+ }
34+ }
35+
36+ var color : Color {
37+ switch self {
38+ case . loading:
39+ return . black
40+ case . signed:
41+ return . green
42+ case . revoked:
43+ return . red
44+ case . unknown:
45+ return . yellow
46+ }
47+ }
48+ }
1449// MARK: - CertificateView (List + Add/Edit launchers)
1550struct CertificateView : View {
1651 @State private var customCertificates : [ CustomCertificate ] = [ ]
52+ @State private var certStatuses : [ String : CertStatus ] = [ : ]
1753 @State private var showAddCertificateSheet = false
1854 @State private var editingCertificate : CustomCertificate ? = nil // Used only for edit sheet (.sheet(item:))
1955 @State private var selectedCert : String ? = nil
2056 @State private var showingDeleteAlert = false
2157 @State private var certToDelete : CustomCertificate ?
2258 @State private var newlyAddedFolder : String ? = nil
23-
59+
2460 var body : some View {
2561 // <-- Removed nested NavigationStack to avoid hiding the title from the parent stack
2662 ScrollView {
@@ -32,6 +68,12 @@ struct CertificateView: View {
3268 . font ( . title2)
3369 . fontWeight ( . semibold)
3470 . foregroundColor ( . primary)
71+ if let status = certStatuses [ cert. folderName] {
72+ Text ( status. description)
73+ . font ( . caption)
74+ . fontWeight ( . medium)
75+ . foregroundColor ( status. color)
76+ }
3577 }
3678 . padding ( 20 )
3779 . frame ( maxWidth: . infinity)
@@ -53,7 +95,7 @@ struct CertificateView: View {
5395 UserDefaults . standard. set ( selectedCert, forKey: " selectedCertificateFolder " )
5496 }
5597 }
56-
98+
5799 HStack {
58100 Button ( action: {
59101 // EDIT: trigger identifiable sheet
@@ -66,9 +108,9 @@ struct CertificateView: View {
66108 . background ( Color ( . systemGray6) . opacity ( 0.8 ) )
67109 . clipShape ( Circle ( ) )
68110 }
69-
111+
70112 Spacer ( )
71-
113+
72114 Button ( action: {
73115 if customCertificates. count > 1 {
74116 certToDelete = cert
@@ -134,13 +176,53 @@ struct CertificateView: View {
134176 reloadCertificatesAndEnsureSelection ( )
135177 }
136178 }
137-
179+
138180 private func reloadCertificatesAndEnsureSelection( ) {
139181 customCertificates = CertificateFileManager . shared. loadCertificates ( )
140182 selectedCert = UserDefaults . standard. string ( forKey: " selectedCertificateFolder " )
141183 ensureSelection ( )
184+ checkStatuses ( )
185+ }
186+
187+ private func checkStatuses( ) {
188+ for cert in customCertificates {
189+ let folderName = cert. folderName
190+ certStatuses [ folderName] = . loading
191+
192+ let certDir = CertificateFileManager . shared. certificatesDirectory. appendingPathComponent ( folderName)
193+ let p12URL = certDir. appendingPathComponent ( " certificate.p12 " )
194+ let provURL = certDir. appendingPathComponent ( " profile.mobileprovision " )
195+ let passwordURL = certDir. appendingPathComponent ( " password.txt " )
196+
197+ let p12Password : String
198+ if let passwordData = try ? Data ( contentsOf: passwordURL) ,
199+ let passwordStr = String ( data: passwordData, encoding: . utf8) ? . trimmingCharacters ( in: . whitespacesAndNewlines) {
200+ p12Password = passwordStr
201+ } else {
202+ p12Password = " "
203+ }
204+
205+ ProStoreTools . checkRevokage (
206+ p12URL: p12URL,
207+ provURL: provURL,
208+ p12Password: p12Password
209+ ) { status, expirationDate, error in
210+ DispatchQueue . main. async {
211+ let newStatus : CertStatus
212+ switch status {
213+ case 0 :
214+ newStatus = . signed( expirationDate)
215+ case 1 , 2 :
216+ newStatus = . revoked( expirationDate)
217+ default :
218+ newStatus = . unknown
219+ }
220+ self . certStatuses [ folderName] = newStatus
221+ }
222+ }
223+ }
142224 }
143-
225+
144226 private func ensureSelection( ) {
145227 if selectedCert == nil || !customCertificates. contains ( where: { $0. folderName == selectedCert } ) {
146228 if let firstCert = customCertificates. first {
@@ -149,11 +231,11 @@ struct CertificateView: View {
149231 }
150232 }
151233 }
152-
234+
153235 private func deleteCertificate( _ cert: CustomCertificate ) {
154236 try ? CertificateFileManager . shared. deleteCertificate ( folderName: cert. folderName)
155237 customCertificates = CertificateFileManager . shared. loadCertificates ( )
156-
238+
157239 if selectedCert == cert. folderName {
158240 if let newSelection = customCertificates. first {
159241 selectedCert = newSelection. folderName
@@ -164,14 +246,15 @@ struct CertificateView: View {
164246 }
165247 }
166248 ensureSelection ( )
249+ checkStatuses ( )
167250 }
168251}
169252// MARK: - Add / Edit View
170253struct AddCertificateView : View {
171254 @Environment ( \. dismiss) private var dismiss
172255 let editingCertificate : CustomCertificate ?
173256 let onSave : ( ( String ) -> Void ) ?
174-
257+
175258 @State private var p12File : CertificateFileItem ?
176259 @State private var provFile : CertificateFileItem ?
177260 @State private var password = " "
@@ -180,12 +263,12 @@ struct AddCertificateView: View {
180263 @State private var errorMessage = " "
181264 @State private var displayName : String = " "
182265 @State private var hasLoadedForEdit = false
183-
266+
184267 init ( editingCertificate: CustomCertificate ? = nil , onSave: ( ( String ) -> Void ) ? = nil ) {
185268 self . editingCertificate = editingCertificate
186269 self . onSave = onSave
187270 }
188-
271+
189272 var body : some View {
190273 // Use a NavigationStack inside the sheet so the sheet has its own nav bar
191274 NavigationStack {
@@ -205,7 +288,7 @@ struct AddCertificateView: View {
205288 }
206289 }
207290 . disabled ( isChecking)
208-
291+
209292 Button ( action: { activeSheet = . prov } ) {
210293 HStack {
211294 Image ( systemName: " gearshape.fill " )
@@ -221,20 +304,20 @@ struct AddCertificateView: View {
221304 }
222305 . disabled ( isChecking)
223306 }
224-
307+
225308 Section ( header: Text ( " Display Name " ) ) {
226309 TextField ( " Optional Display Name " , text: $displayName)
227310 . disabled ( isChecking)
228311 }
229-
312+
230313 Section ( header: Text ( " Password " ) ) {
231314 SecureField ( " Enter Password " , text: $password)
232315 . disabled ( isChecking)
233316 Text ( " Enter the password for the certificate. Leave it blank if there is no password needed. " )
234317 . font ( . caption)
235318 . foregroundColor ( . secondary)
236319 }
237-
320+
238321 if !errorMessage. isEmpty {
239322 Text ( errorMessage)
240323 . foregroundColor ( . red)
@@ -250,7 +333,7 @@ struct AddCertificateView: View {
250333 }
251334 . disabled ( isChecking)
252335 }
253-
336+
254337 ToolbarItem ( placement: . navigationBarTrailing) {
255338 if isChecking {
256339 ProgressView ( )
@@ -283,17 +366,17 @@ struct AddCertificateView: View {
283366 }
284367 } // NavigationStack
285368 }
286-
369+
287370 private func loadForEdit( cert: CustomCertificate ) {
288371 let certFolder = CertificateFileManager . shared. certificatesDirectory. appendingPathComponent ( cert. folderName)
289372 let p12URL = certFolder. appendingPathComponent ( " certificate.p12 " )
290373 let provURL = certFolder. appendingPathComponent ( " profile.mobileprovision " )
291374 let passwordURL = certFolder. appendingPathComponent ( " password.txt " )
292375 let nameURL = certFolder. appendingPathComponent ( " name.txt " )
293-
376+
294377 p12File = CertificateFileItem ( name: " certificate.p12 " , url: p12URL)
295378 provFile = CertificateFileItem ( name: " profile.mobileprovision " , url: provURL)
296-
379+
297380 if let pwData = try ? Data ( contentsOf: passwordURL) , let pw = String ( data: pwData, encoding: . utf8) {
298381 password = pw
299382 }
@@ -303,10 +386,10 @@ struct AddCertificateView: View {
303386 }
304387 private func saveCertificate( ) {
305388 guard let p12URL = p12File? . url, let provURL = provFile? . url else { return }
306-
389+
307390 isChecking = true
308391 errorMessage = " "
309-
392+
310393 let workItem : DispatchWorkItem = DispatchWorkItem {
311394 do {
312395 var p12Data : Data
@@ -326,17 +409,17 @@ struct AddCertificateView: View {
326409 p12Data = try Data ( contentsOf: p12URL)
327410 provData = try Data ( contentsOf: provURL)
328411 }
329-
412+
330413 let checkResult = CertificatesManager . check ( p12Data: p12Data, password: self . password, mobileProvisionData: provData)
331414 var dispatchError : String ?
332-
415+
333416 switch checkResult {
334417 case . success( . success) :
335418 // Generate displayName from cert if not set
336419 if localDisplayName. isEmpty {
337420 localDisplayName = CertificatesManager . getCertificateName ( mobileProvisionData: provData) ?? " Custom Certificate "
338421 }
339-
422+
340423 if let folder = self . editingCertificate? . folderName {
341424 try CertificateFileManager . shared. updateCertificate ( folderName: folder, p12Data: p12Data, provData: provData, password: self . password, displayName: localDisplayName)
342425 } else {
@@ -350,7 +433,7 @@ struct AddCertificateView: View {
350433 case . failure( let error) :
351434 dispatchError = " Error: \( error. localizedDescription) "
352435 }
353-
436+
354437 DispatchQueue . main. async {
355438 self . isChecking = false
356439 if let err = dispatchError {
0 commit comments