11import SwiftUI
22import UniformTypeIdentifiers
3-
43// Centralized types to avoid conflicts
54struct CertificateFileItem {
65 var name : String = " "
@@ -11,7 +10,6 @@ struct CustomCertificate: Identifiable {
1110 let displayName : String
1211 let folderName : String
1312}
14-
1513// MARK: - Date Extension for Formatting
1614extension Date {
1715 func formattedWithOrdinal( ) -> String {
@@ -23,7 +21,6 @@ extension Date {
2321 let year = Calendar . current. component ( . year, from: self )
2422 return " \( ordinal) of \( month) \( year) "
2523 }
26-
2724 private func ordinalSuffix( for number: Int ) -> String {
2825 let suffix : String
2926 let ones = number % 10
@@ -42,7 +39,6 @@ extension Date {
4239 return " \( number) \( suffix) "
4340 }
4441}
45-
4642// MARK: - CertificateView (List + Add/Edit launchers)
4743struct CertificateView : View {
4844 @State private var customCertificates : [ CustomCertificate ] = [ ]
@@ -134,7 +130,6 @@ struct CertificateView: View {
134130 reloadCertificatesAndEnsureSelection ( )
135131 }
136132 }
137-
138133 private func certificateItem( for cert: CustomCertificate ) -> some View {
139134 ZStack ( alignment: . top) {
140135 certificateContent ( for: cert)
@@ -153,7 +148,6 @@ struct CertificateView: View {
153148 certificateButtons ( for: cert)
154149 }
155150 }
156-
157151 private func certificateContent( for cert: CustomCertificate ) -> some View {
158152 VStack ( alignment: . center, spacing: 12 ) {
159153 Text ( cert. displayName)
@@ -163,7 +157,6 @@ struct CertificateView: View {
163157 . lineLimit ( 2 )
164158 . minimumScaleFactor ( 0.8 )
165159 . multilineTextAlignment ( . center)
166-
167160 if let expiry = certExpiries [ cert. folderName] , let validExpiry = expiry {
168161 expiryDisplay ( for: validExpiry)
169162 } else {
@@ -172,13 +165,11 @@ struct CertificateView: View {
172165 . fontWeight ( . medium)
173166 . foregroundColor ( . secondary)
174167 }
175-
176- if let status = certStatuses [ cert. folderName] {
177- Text ( status)
178- . font ( . caption)
179- . fontWeight ( . medium)
180- . foregroundColor ( statusColor ( for: status) )
181- }
168+ let status = certStatuses [ cert. folderName] ?? " Unknown "
169+ Text ( " Status: \( status) " )
170+ . font ( . caption)
171+ . fontWeight ( . medium)
172+ . foregroundColor ( status == " Revoked " ? . red : . secondary)
182173 }
183174 . padding ( 20 )
184175 . frame ( maxWidth: . infinity)
@@ -189,7 +180,6 @@ struct CertificateView: View {
189180 . stroke ( selectedCert == cert. folderName ? Color . blue : Color . clear, lineWidth: 3 )
190181 )
191182 }
192-
193183 private func expiryDisplay( for expiry: Date ) -> some View {
194184 let now = Date ( )
195185 let components = Calendar . current. dateComponents ( [ . day] , from: now, to: expiry)
@@ -207,37 +197,26 @@ struct CertificateView: View {
207197 . fontWeight ( . medium)
208198 . foregroundColor ( . primary)
209199 }
210-
211200 private func certificateBackground( for cert: CustomCertificate ) -> Color {
212- guard let expiry = certExpiries [ cert. folderName] , let validExpiry = expiry,
213- let status = certStatuses [ cert. folderName] else {
201+ let status = certStatuses [ cert. folderName] ?? " Unknown "
202+ if status == " Revoked " {
203+ return . red. opacity ( 0.15 )
204+ }
205+ guard let expiry = certExpiries [ cert. folderName] , expiry != nil else {
214206 return Color . clear
215207 }
216208 let now = Date ( )
217- let components = Calendar . current. dateComponents ( [ . day] , from: now, to: validExpiry )
209+ let components = Calendar . current. dateComponents ( [ . day] , from: now, to: expiry! )
218210 let days = components. day ?? 0
219- if status != " Signed " || days <= 0 {
211+ switch days {
212+ case ..< 0 , 0 :
220213 return . red. opacity ( 0.15 )
221- } else if days <= 30 {
214+ case 1 ... 30 :
222215 return . yellow. opacity ( 0.15 )
223- } else {
224- return . green. opacity ( 0.15 )
225- }
226- }
227-
228- private func statusColor( for status: String ) -> Color {
229- switch status {
230- case " Signed " :
231- return . green
232- case " Revoked " , " Mismatch " :
233- return . red
234- case " Unknown " :
235- return . gray
236216 default :
237- return . secondary
217+ return . green . opacity ( 0.15 )
238218 }
239219 }
240-
241220 private func certificateButtons( for cert: CustomCertificate ) -> some View {
242221 HStack {
243222 Button ( action: {
@@ -251,9 +230,9 @@ struct CertificateView: View {
251230 . background ( Color ( . systemGray6) . opacity ( 0.8 ) )
252231 . clipShape ( Circle ( ) )
253232 }
254-
233+
255234 Spacer ( )
256-
235+
257236 Button ( action: {
258237 if customCertificates. count > 1 {
259238 certToDelete = cert
@@ -276,36 +255,23 @@ struct CertificateView: View {
276255 customCertificates = CertificateFileManager . shared. loadCertificates ( )
277256 selectedCert = UserDefaults . standard. string ( forKey: " selectedCertificateFolder " )
278257 ensureSelection ( )
279- Task {
280- await loadExpiries ( )
258+ loadExpiries ( )
259+ for cert in customCertificates {
260+ Task {
261+ let status = await CertRevokeChecker . checkRevocation ( folderName: cert. folderName)
262+ await MainActor . run {
263+ certStatuses [ cert. folderName] = status
264+ }
265+ }
281266 }
282267 }
283-
284- private func loadExpiries( ) async {
285- certExpiries = [ : ]
286- certStatuses = [ : ]
268+ private func loadExpiries( ) {
287269 for cert in customCertificates {
288270 let folderName = cert. folderName
289271 let certDir = CertificateFileManager . shared. certificatesDirectory. appendingPathComponent ( folderName)
290272 let provURL = certDir. appendingPathComponent ( " profile.mobileprovision " )
291- let p12URL = certDir. appendingPathComponent ( " certificate.p12 " )
292- let passwordURL = certDir. appendingPathComponent ( " password.txt " )
293- let localExpiry = signer. getExpirationDate ( provURL: provURL)
294- let password = ( try ? String ( contentsOf: passwordURL, encoding: . utf8) ) ?? " "
295- let result = await CertRevokeChecker . shared. check ( p12URL: p12URL, provisionURL: provURL, password: password)
296- switch result {
297- case . success( let isSigned, let expires, let match) :
298- if match {
299- certExpiries [ folderName] = expires
300- certStatuses [ folderName] = isSigned ? " Signed " : " Revoked "
301- } else {
302- certStatuses [ folderName] = " Mismatch "
303- certExpiries [ folderName] = localExpiry
304- }
305- case . failure, . networkError:
306- certStatuses [ folderName] = " Unknown "
307- certExpiries [ folderName] = localExpiry
308- }
273+ let expiry = signer. getExpirationDate ( provURL: provURL)
274+ certExpiries [ folderName] = expiry
309275 }
310276 }
311277 private func ensureSelection( ) {
@@ -319,7 +285,7 @@ struct CertificateView: View {
319285 private func deleteCertificate( _ cert: CustomCertificate ) {
320286 try ? CertificateFileManager . shared. deleteCertificate ( folderName: cert. folderName)
321287 customCertificates = CertificateFileManager . shared. loadCertificates ( )
322-
288+
323289 if selectedCert == cert. folderName {
324290 if let newSelection = customCertificates. first {
325291 selectedCert = newSelection. folderName
@@ -330,12 +296,9 @@ struct CertificateView: View {
330296 }
331297 }
332298 ensureSelection ( )
333- Task {
334- await loadExpiries ( )
335- }
299+ loadExpiries ( )
336300 }
337301}
338-
339302// MARK: - Add / Edit View
340303struct AddCertificateView : View {
341304 @Environment ( \. dismiss) private var dismiss
@@ -371,7 +334,7 @@ struct AddCertificateView: View {
371334 }
372335 }
373336 . disabled ( isChecking)
374-
337+
375338 Button ( action: { activeSheet = . prov } ) {
376339 HStack {
377340 Image ( systemName: " gearshape.fill " )
@@ -387,20 +350,20 @@ struct AddCertificateView: View {
387350 }
388351 . disabled ( isChecking)
389352 }
390-
353+
391354 Section ( header: Text ( " Display Name " ) ) {
392355 TextField ( " Optional Display Name " , text: $displayName)
393356 . disabled ( isChecking)
394357 }
395-
358+
396359 Section ( header: Text ( " Password " ) ) {
397360 SecureField ( " Enter Password " , text: $password)
398361 . disabled ( isChecking)
399362 Text ( " Enter the password for the certificate. Leave it blank if there is no password needed. " )
400363 . font ( . caption)
401364 . foregroundColor ( . secondary)
402365 }
403-
366+
404367 if !errorMessage. isEmpty {
405368 Text ( errorMessage)
406369 . foregroundColor ( . red)
@@ -453,24 +416,23 @@ struct AddCertificateView: View {
453416 let provURL = certFolder. appendingPathComponent ( " profile.mobileprovision " )
454417 let passwordURL = certFolder. appendingPathComponent ( " password.txt " )
455418 let nameURL = certFolder. appendingPathComponent ( " name.txt " )
456-
419+
457420 p12File = CertificateFileItem ( name: " certificate.p12 " , url: p12URL)
458421 provFile = CertificateFileItem ( name: " profile.mobileprovision " , url: provURL)
459-
422+
460423 if let pwData = try ? Data ( contentsOf: passwordURL) , let pw = String ( data: pwData, encoding: . utf8) {
461424 password = pw
462425 }
463426 if let nameData = try ? Data ( contentsOf: nameURL) , let nameStr = String ( data: nameData, encoding: . utf8) {
464427 displayName = nameStr
465428 }
466429 }
467-
468430 private func saveCertificate( ) {
469431 guard let p12URL = p12File? . url, let provURL = provFile? . url else { return }
470-
432+
471433 isChecking = true
472434 errorMessage = " "
473-
435+
474436 let workItem : DispatchWorkItem = DispatchWorkItem {
475437 do {
476438 var p12Data : Data
@@ -489,16 +451,16 @@ struct AddCertificateView: View {
489451 p12Data = try Data ( contentsOf: p12URL)
490452 provData = try Data ( contentsOf: provURL)
491453 }
492-
454+
493455 let checkResult = CertificatesManager . check ( p12Data: p12Data, password: self . password, mobileProvisionData: provData)
494456 var dispatchError : String ?
495-
457+
496458 switch checkResult {
497459 case . success( . success) :
498460 if localDisplayName. isEmpty {
499461 localDisplayName = CertificatesManager . getCertificateName ( mobileProvisionData: provData) ?? " Custom Certificate "
500462 }
501-
463+
502464 if let folder = self . editingCertificate? . folderName {
503465 try CertificateFileManager . shared. updateCertificate ( folderName: folder, p12Data: p12Data, provData: provData, password: self . password, displayName: localDisplayName)
504466 } else {
@@ -512,7 +474,7 @@ struct AddCertificateView: View {
512474 case . failure( let error) :
513475 dispatchError = " Error: \( error. localizedDescription) "
514476 }
515-
477+
516478 DispatchQueue . main. async {
517479 self . isChecking = false
518480 if let err = dispatchError {
0 commit comments