@@ -57,89 +57,7 @@ struct CertificateView: View {
5757 ScrollView {
5858 LazyVGrid ( columns: [ GridItem ( . flexible( ) ) , GridItem ( . flexible( ) ) ] , spacing: 20 ) {
5959 ForEach ( customCertificates) { cert in
60- ZStack ( alignment: . top) {
61- VStack ( alignment: . leading, spacing: 12 ) {
62- Text ( cert. displayName)
63- . font ( . title2)
64- . fontWeight ( . semibold)
65- . foregroundColor ( . primary)
66- if let expiry = certExpiries [ cert. folderName] {
67- let now = Date ( )
68- let components = Calendar . current. dateComponents ( [ . day] , from: now, to: expiry)
69- let days = components. day ?? 0
70- let displayDate = expiry. formattedWithOrdinal ( )
71- let expiryText : String
72- if days > 0 {
73- expiryText = " Expires in \( days) days on \( displayDate) "
74- } else {
75- let pastDays = abs ( days)
76- expiryText = " Expired \( pastDays) days ago on \( displayDate) "
77- }
78- Text ( expiryText)
79- . font ( . caption)
80- . fontWeight ( . medium)
81- . foregroundColor ( . primary)
82- } else {
83- Text ( " No expiry date " )
84- . font ( . caption)
85- . fontWeight ( . medium)
86- . foregroundColor ( . secondary)
87- }
88- }
89- . padding ( 20 )
90- . frame ( maxWidth: . infinity)
91- . background ( backgroundColor ( for: cert. folderName) )
92- . cornerRadius ( 16 )
93- . overlay (
94- RoundedRectangle ( cornerRadius: 16 )
95- . stroke ( selectedCert == cert. folderName ? Color . blue : Color . clear, lineWidth: 3 )
96- )
97- . onTapGesture {
98- // Only allow deselection if there are other certificates available
99- if selectedCert == cert. folderName && customCertificates. count > 1 {
100- if let nextCert = customCertificates. first ( where: { $0. folderName != cert. folderName } ) {
101- selectedCert = nextCert. folderName
102- UserDefaults . standard. set ( selectedCert, forKey: " selectedCertificateFolder " )
103- }
104- } else {
105- selectedCert = cert. folderName
106- UserDefaults . standard. set ( selectedCert, forKey: " selectedCertificateFolder " )
107- }
108- }
109-
110- HStack {
111- Button ( action: {
112- // EDIT: trigger identifiable sheet
113- editingCertificate = cert
114- } ) {
115- Image ( systemName: " pencil " )
116- . foregroundColor ( . blue)
117- . font ( . caption)
118- . padding ( 8 )
119- . background ( Color ( . systemGray6) . opacity ( 0.8 ) )
120- . clipShape ( Circle ( ) )
121- }
122-
123- Spacer ( )
124-
125- Button ( action: {
126- if customCertificates. count > 1 {
127- certToDelete = cert
128- showingDeleteAlert = true
129- }
130- } ) {
131- Image ( systemName: " trash " )
132- . foregroundColor ( customCertificates. count > 1 ? . red : . gray)
133- . font ( . caption)
134- . padding ( 8 )
135- . background ( Color ( . systemGray6) . opacity ( 0.8 ) )
136- . clipShape ( Circle ( ) )
137- }
138- . disabled ( customCertificates. count <= 1 )
139- }
140- . padding ( . top, 12 )
141- . padding ( . horizontal, 12 )
142- }
60+ certificateItem ( for: cert)
14361 }
14462 }
14563 . padding ( )
@@ -188,8 +106,70 @@ struct CertificateView: View {
188106 }
189107 }
190108
191- private func backgroundColor( for folderName: String ) -> Color {
192- guard let expiry = certExpiries [ folderName] , expiry != nil else {
109+ private func certificateItem( for cert: CustomCertificate ) -> some View {
110+ ZStack ( alignment: . top) {
111+ certificateContent ( for: cert)
112+ . onTapGesture {
113+ // Only allow deselection if there are other certificates available
114+ if selectedCert == cert. folderName && customCertificates. count > 1 {
115+ if let nextCert = customCertificates. first ( where: { $0. folderName != cert. folderName } ) {
116+ selectedCert = nextCert. folderName
117+ UserDefaults . standard. set ( selectedCert, forKey: " selectedCertificateFolder " )
118+ }
119+ } else {
120+ selectedCert = cert. folderName
121+ UserDefaults . standard. set ( selectedCert, forKey: " selectedCertificateFolder " )
122+ }
123+ }
124+ certificateButtons ( for: cert)
125+ }
126+ }
127+
128+ private func certificateContent( for cert: CustomCertificate ) -> some View {
129+ VStack ( alignment: . leading, spacing: 12 ) {
130+ Text ( cert. displayName)
131+ . font ( . title2)
132+ . fontWeight ( . semibold)
133+ . foregroundColor ( . primary)
134+ if let expiry = certExpiries [ cert. folderName] {
135+ expiryDisplay ( for: expiry)
136+ } else {
137+ Text ( " No expiry date " )
138+ . font ( . caption)
139+ . fontWeight ( . medium)
140+ . foregroundColor ( . secondary)
141+ }
142+ }
143+ . padding ( 20 )
144+ . frame ( maxWidth: . infinity)
145+ . background ( certificateBackground ( for: cert) )
146+ . cornerRadius ( 16 )
147+ . overlay (
148+ RoundedRectangle ( cornerRadius: 16 )
149+ . stroke ( selectedCert == cert. folderName ? Color . blue : Color . clear, lineWidth: 3 )
150+ )
151+ }
152+
153+ private func expiryDisplay( for expiry: Date ) -> some View {
154+ let now = Date ( )
155+ let components = Calendar . current. dateComponents ( [ . day] , from: now, to: expiry)
156+ let days = components. day ?? 0
157+ let displayDate = expiry. formattedWithOrdinal ( )
158+ let expiryText : String
159+ if days > 0 {
160+ expiryText = " Expires in \( days) days on \( displayDate) "
161+ } else {
162+ let pastDays = abs ( days)
163+ expiryText = " Expired \( pastDays) days ago on \( displayDate) "
164+ }
165+ return Text ( expiryText)
166+ . font ( . caption)
167+ . fontWeight ( . medium)
168+ . foregroundColor ( . primary)
169+ }
170+
171+ private func certificateBackground( for cert: CustomCertificate ) -> Color {
172+ guard let expiry = certExpiries [ cert. folderName] , expiry != nil else {
193173 return Color . clear
194174 }
195175 let now = Date ( )
@@ -204,6 +184,41 @@ struct CertificateView: View {
204184 return . green. opacity ( 0.15 )
205185 }
206186 }
187+
188+ private func certificateButtons( for cert: CustomCertificate ) -> some View {
189+ HStack {
190+ Button ( action: {
191+ // EDIT: trigger identifiable sheet
192+ editingCertificate = cert
193+ } ) {
194+ Image ( systemName: " pencil " )
195+ . foregroundColor ( . blue)
196+ . font ( . caption)
197+ . padding ( 8 )
198+ . background ( Color ( . systemGray6) . opacity ( 0.8 ) )
199+ . clipShape ( Circle ( ) )
200+ }
201+
202+ Spacer ( )
203+
204+ Button ( action: {
205+ if customCertificates. count > 1 {
206+ certToDelete = cert
207+ showingDeleteAlert = true
208+ }
209+ } ) {
210+ Image ( systemName: " trash " )
211+ . foregroundColor ( customCertificates. count > 1 ? . red : . gray)
212+ . font ( . caption)
213+ . padding ( 8 )
214+ . background ( Color ( . systemGray6) . opacity ( 0.8 ) )
215+ . clipShape ( Circle ( ) )
216+ }
217+ . disabled ( customCertificates. count <= 1 )
218+ }
219+ . padding ( . top, 12 )
220+ . padding ( . horizontal, 12 )
221+ }
207222
208223 private func reloadCertificatesAndEnsureSelection( ) {
209224 customCertificates = CertificateFileManager . shared. loadCertificates ( )
0 commit comments