@@ -18,81 +18,87 @@ struct CustomCertificate: Identifiable {
1818struct CertificateView : View {
1919 @State private var customCertificates : [ CustomCertificate ] = [ ]
2020 @State private var showAddCertificateSheet = false
21- @State private var editingCertificate : CustomCertificate ? = nil // Used only for edit sheet (.sheet(item:))
21+ @State private var editingCertificate : CustomCertificate ? = nil // Used only for edit sheet (.sheet(item:))
2222 @State private var selectedCert : String ? = nil
2323 @State private var showingDeleteAlert = false
2424 @State private var certToDelete : CustomCertificate ?
25-
25+
2626 var body : some View {
2727 // <-- Removed nested NavigationStack to avoid hiding the title from the parent stack
28- ScrollView {
29- LazyVGrid ( columns: [ GridItem ( . flexible( ) ) , GridItem ( . flexible( ) ) ] , spacing: 20 ) {
30- ForEach ( customCertificates) { cert in
31- ZStack ( alignment: . top) {
32- VStack ( alignment: . leading, spacing: 12 ) {
33- Text ( cert. displayName)
34- . font ( . title2)
35- . fontWeight ( . semibold)
36- . foregroundColor ( . primary)
37- }
38- . padding ( 20 )
39- . frame ( maxWidth: . infinity)
40- . background ( Color ( . systemGray6) )
41- . cornerRadius ( 16 )
42- . overlay (
43- RoundedRectangle ( cornerRadius: 16 )
44- . stroke ( selectedCert == cert. folderName ? Color . blue : Color . clear, lineWidth: 3 )
45- )
46- . onTapGesture {
47- // Only allow deselection if there are other certificates available
48- if selectedCert == cert. folderName && customCertificates. count > 1 {
49- if let nextCert = customCertificates. first ( where: { $0. folderName != cert. folderName } ) {
50- selectedCert = nextCert. folderName
28+ List {
29+ Section {
30+ LazyVGrid ( columns: [ GridItem ( . flexible( ) ) , GridItem ( . flexible( ) ) ] , spacing: 20 ) {
31+ ForEach ( customCertificates) { cert in
32+ ZStack ( alignment: . top) {
33+ VStack ( alignment: . leading, spacing: 12 ) {
34+ Text ( cert. displayName)
35+ . font ( . title2)
36+ . fontWeight ( . semibold)
37+ . foregroundColor ( . primary)
38+ }
39+ . padding ( 20 )
40+ . frame ( maxWidth: . infinity)
41+ . background ( Color ( . systemGray6) )
42+ . cornerRadius ( 16 )
43+ . overlay (
44+ RoundedRectangle ( cornerRadius: 16 )
45+ . stroke ( selectedCert == cert. folderName ? Color . blue : Color . clear, lineWidth: 3 )
46+ )
47+ . onTapGesture {
48+ // Only allow deselection if there are other certificates available
49+ if selectedCert == cert. folderName && customCertificates. count > 1 {
50+ if let nextCert = customCertificates. first ( where: { $0. folderName != cert. folderName } ) {
51+ selectedCert = nextCert. folderName
52+ UserDefaults . standard. set ( selectedCert, forKey: " selectedCertificateFolder " )
53+ }
54+ } else {
55+ selectedCert = cert. folderName
5156 UserDefaults . standard. set ( selectedCert, forKey: " selectedCertificateFolder " )
5257 }
53- } else {
54- selectedCert = cert. folderName
55- UserDefaults . standard. set ( selectedCert, forKey: " selectedCertificateFolder " )
56- }
57- }
58-
59- HStack {
60- Button ( action: {
61- // EDIT: trigger identifiable sheet
62- editingCertificate = cert
63- } ) {
64- Image ( systemName: " pencil " )
65- . foregroundColor ( . blue)
66- . font ( . caption)
67- . padding ( 8 )
68- . background ( Color . white. opacity ( 0.8 ) )
69- . clipShape ( Circle ( ) )
7058 }
71-
72- Spacer ( )
73-
74- Button ( action: {
75- if customCertificates. count > 1 {
76- certToDelete = cert
77- showingDeleteAlert = true
59+
60+ HStack {
61+ Button ( action: {
62+ // EDIT: trigger identifiable sheet
63+ editingCertificate = cert
64+ } ) {
65+ Image ( systemName: " pencil " )
66+ . foregroundColor ( . blue)
67+ . font ( . caption)
68+ . padding ( 8 )
69+ . background ( Color . white. opacity ( 0.8 ) )
70+ . clipShape ( Circle ( ) )
7871 }
79- } ) {
80- Image ( systemName: " trash " )
81- . foregroundColor ( customCertificates. count > 1 ? . red : . gray)
82- . font ( . caption)
83- . padding ( 8 )
84- . background ( Color . white. opacity ( 0.8 ) )
85- . clipShape ( Circle ( ) )
72+
73+ Spacer ( )
74+
75+ Button ( action: {
76+ if customCertificates. count > 1 {
77+ certToDelete = cert
78+ showingDeleteAlert = true
79+ }
80+ } ) {
81+ Image ( systemName: " trash " )
82+ . foregroundColor ( customCertificates. count > 1 ? . red : . gray)
83+ . font ( . caption)
84+ . padding ( 8 )
85+ . background ( Color . white. opacity ( 0.8 ) )
86+ . clipShape ( Circle ( ) )
87+ }
88+ . disabled ( customCertificates. count <= 1 )
8689 }
87- . disabled ( customCertificates. count <= 1 )
90+ . padding ( . top, 12 )
91+ . padding ( . horizontal, 12 )
8892 }
89- . padding ( . top, 12 )
90- . padding ( . horizontal, 12 )
9193 }
9294 }
95+ . padding ( . vertical)
9396 }
94- . padding ( )
97+ . listRowInsets ( EdgeInsets ( ) )
9598 }
99+ . listStyle ( . plain)
100+ . navigationTitle ( " ProSign - Certificates " )
101+ . navigationBarTitleDisplayMode ( . large)
96102 . background ( Color ( . systemGray6) )
97103 . toolbar {
98104 ToolbarItem ( placement: . navigationBarTrailing) {
@@ -131,13 +137,13 @@ struct CertificateView: View {
131137 reloadCertificatesAndEnsureSelection ( )
132138 }
133139 }
134-
140+
135141 private func reloadCertificatesAndEnsureSelection( ) {
136142 customCertificates = CertificateFileManager . shared. loadCertificates ( )
137143 selectedCert = UserDefaults . standard. string ( forKey: " selectedCertificateFolder " )
138144 ensureSelection ( )
139145 }
140-
146+
141147 private func ensureSelection( ) {
142148 if selectedCert == nil || !customCertificates. contains ( where: { $0. folderName == selectedCert } ) {
143149 if let firstCert = customCertificates. first {
@@ -146,11 +152,11 @@ struct CertificateView: View {
146152 }
147153 }
148154 }
149-
155+
150156 private func deleteCertificate( _ cert: CustomCertificate ) {
151157 try ? CertificateFileManager . shared. deleteCertificate ( folderName: cert. folderName)
152158 customCertificates = CertificateFileManager . shared. loadCertificates ( )
153-
159+
154160 if selectedCert == cert. folderName {
155161 if let newSelection = customCertificates. first {
156162 selectedCert = newSelection. folderName
@@ -168,7 +174,7 @@ struct CertificateView: View {
168174struct AddCertificateView : View {
169175 @Environment ( \. dismiss) private var dismiss
170176 let editingCertificate : CustomCertificate ?
171-
177+
172178 @State private var p12File : CertificateFileItem ?
173179 @State private var provFile : CertificateFileItem ?
174180 @State private var password = " "
@@ -177,11 +183,11 @@ struct AddCertificateView: View {
177183 @State private var errorMessage = " "
178184 @State private var displayName : String = " "
179185 @State private var hasLoadedForEdit = false
180-
186+
181187 init ( editingCertificate: CustomCertificate ? = nil ) {
182188 self . editingCertificate = editingCertificate
183189 }
184-
190+
185191 var body : some View {
186192 // Use a NavigationStack inside the sheet so the sheet has its own nav bar
187193 NavigationStack {
@@ -201,7 +207,7 @@ struct AddCertificateView: View {
201207 }
202208 }
203209 . disabled ( isChecking)
204-
210+
205211 Button ( action: { activeSheet = . prov } ) {
206212 HStack {
207213 Image ( systemName: " gearshape.fill " )
@@ -217,20 +223,20 @@ struct AddCertificateView: View {
217223 }
218224 . disabled ( isChecking)
219225 }
220-
226+
221227 Section ( header: Text ( " Display Name " ) ) {
222228 TextField ( " Optional Display Name " , text: $displayName)
223229 . disabled ( isChecking)
224230 }
225-
231+
226232 Section ( header: Text ( " Password " ) ) {
227233 SecureField ( " Enter Password " , text: $password)
228234 . disabled ( isChecking)
229235 Text ( " Enter the password for the certificate. Leave it blank if there is no password needed. " )
230236 . font ( . caption)
231237 . foregroundColor ( . secondary)
232238 }
233-
239+
234240 if !errorMessage. isEmpty {
235241 Text ( errorMessage)
236242 . foregroundColor ( . red)
@@ -246,7 +252,7 @@ struct AddCertificateView: View {
246252 }
247253 . disabled ( isChecking)
248254 }
249-
255+
250256 ToolbarItem ( placement: . navigationBarTrailing) {
251257 if isChecking {
252258 ProgressView ( )
@@ -279,36 +285,35 @@ struct AddCertificateView: View {
279285 }
280286 } // NavigationStack
281287 }
282-
288+
283289 private func loadForEdit( cert: CustomCertificate ) {
284290 let certFolder = CertificateFileManager . shared. certificatesDirectory. appendingPathComponent ( cert. folderName)
285291 let p12URL = certFolder. appendingPathComponent ( " certificate.p12 " )
286292 let provURL = certFolder. appendingPathComponent ( " profile.mobileprovision " )
287293 let passwordURL = certFolder. appendingPathComponent ( " password.txt " )
288294 let nameURL = certFolder. appendingPathComponent ( " name.txt " )
289-
295+
290296 p12File = CertificateFileItem ( name: " certificate.p12 " , url: p12URL)
291297 provFile = CertificateFileItem ( name: " profile.mobileprovision " , url: provURL)
292-
298+
293299 if let pwData = try ? Data ( contentsOf: passwordURL) , let pw = String ( data: pwData, encoding: . utf8) {
294300 password = pw
295301 }
296302 if let nameData = try ? Data ( contentsOf: nameURL) , let nameStr = String ( data: nameData, encoding: . utf8) {
297303 displayName = nameStr
298304 }
299305 }
300-
301306 private func saveCertificate( ) {
302307 guard let p12URL = p12File? . url, let provURL = provFile? . url else { return }
303-
308+
304309 isChecking = true
305310 errorMessage = " "
306-
311+
307312 let workItem : DispatchWorkItem = DispatchWorkItem {
308313 do {
309314 var p12Data : Data
310315 var provData : Data
311- var localDisplayName = self . displayName // Local copy to modify if needed
316+ var localDisplayName = self . displayName // Local copy to modify if needed
312317 if self . editingCertificate != nil {
313318 p12Data = try Data ( contentsOf: p12URL)
314319 provData = try Data ( contentsOf: provURL)
@@ -323,17 +328,17 @@ struct AddCertificateView: View {
323328 p12Data = try Data ( contentsOf: p12URL)
324329 provData = try Data ( contentsOf: provURL)
325330 }
326-
331+
327332 let checkResult = CertificatesManager . check ( p12Data: p12Data, password: self . password, mobileProvisionData: provData)
328333 var dispatchError : String ?
329-
334+
330335 switch checkResult {
331336 case . success( . success) :
332337 // Generate displayName from cert if not set
333338 if localDisplayName. isEmpty {
334339 localDisplayName = CertificatesManager . getCertificateName ( mobileProvisionData: provData) ?? " Custom Certificate "
335340 }
336-
341+
337342 if let folder = self . editingCertificate? . folderName {
338343 try CertificateFileManager . shared. updateCertificate ( folderName: folder, p12Data: p12Data, provData: provData, password: self . password, displayName: localDisplayName)
339344 } else {
@@ -346,7 +351,7 @@ struct AddCertificateView: View {
346351 case . failure( let error) :
347352 dispatchError = " Error: \( error. localizedDescription) "
348353 }
349-
354+
350355 DispatchQueue . main. async {
351356 self . isChecking = false
352357 if let err = dispatchError {
@@ -364,6 +369,4 @@ struct AddCertificateView: View {
364369 }
365370 DispatchQueue . global ( qos: . userInitiated) . async ( execute: workItem)
366371 }
367-
368372}
369-
0 commit comments