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

Commit 909ed52

Browse files
authored
Refactor CertificateView to use List instead of ScrollView
1 parent 5d97c0d commit 909ed52

1 file changed

Lines changed: 88 additions & 85 deletions

File tree

Sources/prosign/views/CertificateView.swift

Lines changed: 88 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -18,81 +18,87 @@ struct CustomCertificate: Identifiable {
1818
struct 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 {
168174
struct 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

Comments
 (0)