@@ -131,123 +131,23 @@ struct ProfileView: View {
131131 @State private var confirmRemove = false
132132 @State private var removeTargetName : String = " "
133133 @State private var removeTargetUUID : String = " "
134-
135-
134+
135+ // Computed strings to reduce body complexity
136+ private var removeProfileText : String {
137+ String ( format: " Remove profile for %@ (UUID: %@)? \n Apps associated with this profile may become unavailable. " . localized, removeTargetName, removeTargetUUID)
138+ }
139+
136140 var body : some View {
137141 NavigationStack {
138142 List {
139- if working && entries. isEmpty {
140- Section {
141- HStack {
142- Spacer ( )
143- ProgressView ( " Loading... " )
144- Spacer ( )
145- }
146- }
147- } else if entries. isEmpty && notMatchedProfiles. isEmpty {
148- Section {
149- Text ( " No profiles found. " )
150- . foregroundStyle ( . secondary)
151- }
152- }
153-
154- ForEach ( entries) { entry in
155- Section {
156- // Header/Status Row
157- VStack ( alignment: . leading, spacing: 4 ) {
158- if let match = entry. bestMatchingProfile {
159- HStack {
160- Image ( systemName: " clock " )
161- Text ( String ( format: " Expires: %@ " ) , match. profile. formattedDate)
162- }
163- . foregroundStyle ( match. profile. dateColor)
164- . font ( . subheadline)
165- } else {
166- HStack {
167- Image ( systemName: " exclamationmark.triangle " )
168- Text ( " No matching profile " )
169- }
170- . font ( . subheadline)
171- . foregroundColor ( . refreshRed)
172- }
173-
174- Text ( entry. id)
175- . font ( . caption. monospaced ( ) )
176- . foregroundStyle ( . secondary)
177- . textSelection ( . enabled)
178- }
179-
180- // Profiles
181- if let recent = entry. mostRecentProfile {
182- profileRow ( match: recent, isMostRecent: true )
183- }
184-
185- if let best = entry. bestMatchingProfile, best. profile. uuid != entry. mostRecentProfile? . profile. uuid {
186- profileRow ( match: best, isMostRecent: false )
187- }
188-
189- if entry. profileMatches. count > 1 {
190- let showMore = expandedApps. contains ( entry. id)
191- let extraProfiles = entry. profileMatches. dropFirst ( recentAndBestCount ( for: entry) )
192-
193- if !extraProfiles. isEmpty {
194- if showMore {
195- ForEach ( extraProfiles, id: \. profile. uuid) { match in
196- profileRow ( match: match, isMostRecent: false )
197- }
198- }
199-
200- Button {
201- withAnimation {
202- if showMore { expandedApps. remove ( entry. id) }
203- else { expandedApps. insert ( entry. id) }
204- }
205- } label: {
206- Label ( showMore ? String ( format: " Hide older profiles " . localized) : String ( format: " Show %d older profiles " . localized, extraProfiles. count) ,
207- systemImage: showMore ? " chevron.up " : " chevron.down " )
208- . font ( . caption)
209- . foregroundStyle ( . blue)
210- }
211- }
212- }
213- } header: {
214- Text ( entry. name)
215- }
216- }
217-
218- if !notMatchedProfiles. isEmpty {
219- Section ( " Other Profiles " ) {
220- ForEach ( notMatchedProfiles) { entry in
221- VStack ( alignment: . leading) {
222- Text ( entry. id)
223- . font ( . caption. monospaced ( ) )
224- . foregroundStyle ( . primary)
225- }
226-
227- ForEach ( entry. profileMatches, id: \. profile. uuid) { match in
228- profileRow ( match: match, isMostRecent: false )
229- }
230- }
231- }
232- }
143+ loadingOrEmptySection
144+ appEntriesSection
145+ otherProfilesSection
233146 }
234147 . listStyle ( . insetGrouped)
235148 . navigationTitle ( " App Expiry " )
236149 . toolbar {
237- ToolbarItemGroup ( placement: . navigationBarTrailing) {
238- Button {
239- isImporterPresented = true
240- } label: {
241- Label ( " Add " , systemImage: " plus " )
242- }
243-
244- Button {
245- Task { await loadData ( force: true ) }
246- } label: {
247- Label ( " Reload " , systemImage: " arrow.clockwise " )
248- }
249-
250- }
150+ toolbarContent
251151 }
252152 . onAppear { Task { await loadData ( ) } }
253153 . fileImporter (
@@ -271,7 +171,7 @@ struct ProfileView: View {
271171 }
272172 }
273173 }
274- . alert ( alertTitle, isPresented: $alert) {
174+ . alert ( alertTitle, isPresented: $alert) {
275175 Button ( " OK " , role: . cancel) { }
276176 } message: {
277177 Text ( alertMsg)
@@ -282,10 +182,142 @@ struct ProfileView: View {
282182 }
283183 Button ( " Cancel " , role: . cancel) { }
284184 } message: {
285- Text ( String ( format : " Remove profile for %@ (UUID: %@)? \n Apps associated with this profile may become unavailable. " . localized , removeTargetName , removeTargetUUID ) )
185+ Text ( removeProfileText )
286186 }
287187 }
288-
188+
189+ @ViewBuilder
190+ private var loadingOrEmptySection : some View {
191+ if working && entries. isEmpty {
192+ Section {
193+ HStack {
194+ Spacer ( )
195+ ProgressView ( " Loading... " )
196+ Spacer ( )
197+ }
198+ }
199+ } else if entries. isEmpty && notMatchedProfiles. isEmpty {
200+ Section {
201+ Text ( " No profiles found. " )
202+ . foregroundStyle ( . secondary)
203+ }
204+ }
205+ }
206+
207+ @ViewBuilder
208+ private var appEntriesSection : some View {
209+ ForEach ( entries) { entry in
210+ Section {
211+ entryStatusRow ( entry: entry)
212+ profilesList ( for: entry)
213+ } header: {
214+ Text ( entry. name)
215+ }
216+ }
217+ }
218+
219+ @ViewBuilder
220+ private var otherProfilesSection : some View {
221+ if !notMatchedProfiles. isEmpty {
222+ Section ( " Other Profiles " ) {
223+ ForEach ( notMatchedProfiles) { entry in
224+ VStack ( alignment: . leading) {
225+ Text ( entry. id)
226+ . font ( . caption. monospaced ( ) )
227+ . foregroundStyle ( . primary)
228+ }
229+
230+ ForEach ( entry. profileMatches, id: \. profile. uuid) { match in
231+ profileRow ( match: match, isMostRecent: false )
232+ }
233+ }
234+ }
235+ }
236+ }
237+
238+ @ToolbarContentBuilder
239+ private var toolbarContent : some ToolbarContent {
240+ ToolbarItemGroup ( placement: . navigationBarTrailing) {
241+ Button {
242+ isImporterPresented = true
243+ } label: {
244+ Label ( " Add " , systemImage: " plus " )
245+ }
246+
247+ Button {
248+ Task { await loadData ( force: true ) }
249+ } label: {
250+ Label ( " Reload " , systemImage: " arrow.clockwise " )
251+ }
252+ }
253+ }
254+
255+ @ViewBuilder
256+ private func entryStatusRow( entry: AppProfileStatus ) -> some View {
257+ VStack ( alignment: . leading, spacing: 4 ) {
258+ if let match = entry. bestMatchingProfile {
259+ HStack {
260+ Image ( systemName: " clock " )
261+ Text ( String ( format: " Expires: %@ " , match. profile. formattedDate) )
262+ }
263+ . foregroundStyle ( match. profile. dateColor)
264+ . font ( . subheadline)
265+ } else {
266+ HStack {
267+ Image ( systemName: " exclamationmark.triangle " )
268+ Text ( " No matching profile " )
269+ }
270+ . font ( . subheadline)
271+ . foregroundColor ( . refreshRed)
272+ }
273+
274+ Text ( entry. id)
275+ . font ( . caption. monospaced ( ) )
276+ . foregroundStyle ( . secondary)
277+ . textSelection ( . enabled)
278+ }
279+ }
280+
281+ @ViewBuilder
282+ private func profilesList( for entry: AppProfileStatus ) -> some View {
283+ if let recent = entry. mostRecentProfile {
284+ profileRow ( match: recent, isMostRecent: true )
285+ }
286+
287+ if let best = entry. bestMatchingProfile, best. profile. uuid != entry. mostRecentProfile? . profile. uuid {
288+ profileRow ( match: best, isMostRecent: false )
289+ }
290+
291+ if entry. profileMatches. count > 1 {
292+ let showMore = expandedApps. contains ( entry. id)
293+ let extraProfiles = entry. profileMatches. dropFirst ( recentAndBestCount ( for: entry) )
294+
295+ if !extraProfiles. isEmpty {
296+ if showMore {
297+ ForEach ( extraProfiles, id: \. profile. uuid) { match in
298+ profileRow ( match: match, isMostRecent: false )
299+ }
300+ }
301+
302+ showMoreButton ( showMore: showMore, extraCount: extraProfiles. count, entryId: entry. id)
303+ }
304+ }
305+ }
306+
307+ private func showMoreButton( showMore: Bool , extraCount: Int , entryId: String ) -> some View {
308+ Button {
309+ withAnimation {
310+ if showMore { expandedApps. remove ( entryId) }
311+ else { expandedApps. insert ( entryId) }
312+ }
313+ } label: {
314+ Label ( showMore ? " Hide older profiles " . localized : String ( format: " Show %d older profiles " . localized, extraCount) ,
315+ systemImage: showMore ? " chevron.up " : " chevron.down " )
316+ . font ( . caption)
317+ . foregroundStyle ( . blue)
318+ }
319+ }
320+
289321 private func profileActionButton( icon: String , color: Color , action: @escaping ( ) -> Void ) -> some View {
290322 Button ( action: action) {
291323 Image ( systemName: icon)
0 commit comments