@@ -16,70 +16,58 @@ import SwiftUI
1616
1717/// A view with controls for downloading the app's on-demand resources.
1818struct DownloadOfflineResourcesView : View {
19- /// The samples that require offline resources.
20- let samples = SamplesApp . samples. filter ( \. hasDependencies)
21-
2219 /// The action to dismiss the view.
2320 @Environment ( \. dismiss) private var dismiss
2421
22+ /// The progress of a download-all operation.
23+ @State private var downloadAllProgress : Progress ?
24+
2525 /// The view models for the on-demand resource requests.
2626 @State private var onDemandResources : [ String : OnDemandResource ] = [ : ]
2727
28- /// The current state of the "download all on-demand resources" request .
29- @ State private var downloadAllRequestState : OnDemandResource . RequestState = . notStarted
28+ /// A Boolean value indicating whether all of the `onDemandResources` have successfully downloaded .
29+ private var allResourcesAreDownloaded : Bool { uniqueRequestStates == [ . downloaded ] }
3030
31- /// A Boolean value indicating whether confirm cancel alert is showing .
32- @ State private var confirmCancelAlertIsShowing = false
31+ /// A Boolean value indicating whether there is an on going download-all operation .
32+ private var isDownloadingAll : Bool { downloadAllProgress != nil }
3333
34- /// A Boolean value indicating whether all of the `onDemandResources` have successfully downloaded.
35- private var allResourcesAreDownloaded : Bool {
36- guard !onDemandResources. isEmpty else { return false }
37- return onDemandResources. values
38- . allSatisfy { $0. requestState == . downloaded }
39- }
34+ /// A Boolean value indicating whether there is an in progress download.
35+ private var isDownloadingResource : Bool { uniqueRequestStates. contains ( . inProgress) }
4036
41- /// Returns the on-demand resource for the given sample.
42- func resource( for sample: Sample ) -> OnDemandResource ? {
43- return onDemandResources [ sample. name]
37+ /// The distinct request states of all on-demand resources.
38+ private var uniqueRequestStates : Set < OnDemandResource . RequestState > {
39+ onDemandResources. values. reduce ( into: Set ( ) ) { result, resource in
40+ result. insert ( resource. requestState)
41+ }
4442 }
4543
4644 var body : some View {
4745 NavigationStack {
4846 Form {
4947 Section {
5048 Button {
51- downloadAllRequestState = . inProgress
49+ downloadAllProgress = Progress ( )
5250 } label: {
5351 Label {
5452 Text ( " Download All " )
5553 } icon: {
56- switch downloadAllRequestState {
57- case . inProgress :
58- ProgressView ( )
59- case . downloaded :
54+ if let downloadAllProgress {
55+ ProgressView ( downloadAllProgress )
56+ . progressViewStyle ( GaugeProgressViewStyle ( ) )
57+ } else if allResourcesAreDownloaded {
6058 Image ( systemName: " checkmark.circle " )
6159 . foregroundStyle ( . secondary)
62- default :
60+ } else {
6361 Image ( systemName: " arrow.down.circle " )
6462 }
6563 }
6664 . frame ( maxWidth: . infinity)
6765 }
68- . disabled ( onDemandResources. isEmpty || downloadAllRequestState != . notStarted || allResourcesAreDownloaded)
69- . task ( id: downloadAllRequestState) {
70- guard downloadAllRequestState == . inProgress else { return }
71- await downloadAll ( )
72- }
73- . onChange ( of: allResourcesAreDownloaded) {
74- guard allResourcesAreDownloaded else { return }
75- downloadAllRequestState = . downloaded
76- }
66+ . disabled ( onDemandResources. isEmpty || isDownloadingAll || allResourcesAreDownloaded)
7767 }
7868 Section {
79- List ( samples, id: \. name) { sample in
80- if let resource = resource ( for: sample) {
81- DownloadOnDemandResourceView ( name: sample. name, resource: resource)
82- }
69+ List ( onDemandResources. sorted ( by: { $0. key < $1. key } ) , id: \. key) { name, resource in
70+ DownloadOnDemandResourceView ( name: name, resource: resource)
8371 }
8472 } footer: {
8573 Text ( " **Note**: The system may purge downloads at any time. " )
@@ -89,29 +77,24 @@ struct DownloadOfflineResourcesView: View {
8977 . navigationBarTitleDisplayMode ( . inline)
9078 . toolbar {
9179 ToolbarItem ( placement: . confirmationAction) {
92- Button ( " Done " ) {
93- if onDemandResources. values. allSatisfy ( { $0. requestState != . inProgress } ) {
94- dismiss ( )
95- } else {
96- confirmCancelAlertIsShowing = true
97- }
98- }
99- . alert ( " Cancel downloads? " , isPresented: $confirmCancelAlertIsShowing) {
100- Button ( " Don't Cancel " , role: . cancel, action: { } )
101-
80+ if isDownloadingResource {
10281 Button ( " Cancel " ) {
10382 for resource in onDemandResources. values where resource. requestState == . inProgress {
10483 resource. cancel ( )
10584 }
10685 dismiss ( )
10786 }
87+ } else {
88+ Button ( " Done " ) {
89+ dismiss ( )
90+ }
10891 }
10992 }
11093 }
11194 . task {
11295 guard onDemandResources. isEmpty else { return }
11396 onDemandResources = await withTaskGroup { group in
114- for sample in samples {
97+ for sample in SamplesApp . samples where sample . hasDependencies {
11598 group. addTask {
11699 let resource = await OnDemandResource ( tags: sample. odrTags)
117100 return ( sample. name, resource)
@@ -124,22 +107,30 @@ struct DownloadOfflineResourcesView: View {
124107 return resources
125108 }
126109 }
110+ . task ( id: isDownloadingAll) {
111+ guard isDownloadingAll else { return }
112+ await downloadAll ( )
113+ }
114+ . onChange ( of: isDownloadingResource) {
115+ guard isDownloadingAll, !isDownloadingResource else { return }
116+ downloadAllProgress = nil
117+ }
127118 }
128119 }
129120
130121 /// Downloads all of the on-demand resources that haven't started a request yet.
131122 /// - Note: The system may purge the resources at any time after the request object is deallocated.
132123 private func downloadAll( ) async {
133124 await withTaskGroup { group in
134- for resource in onDemandResources. values where resource. isDownloadable {
135- group. addTask ( operation: resource. download)
125+ for resource in onDemandResources. values {
126+ if resource. isDownloadable {
127+ group. addTask ( operation: resource. download)
128+ downloadAllProgress? . addChildUnits ( resource. progress)
129+ } else if resource. requestState == . inProgress {
130+ downloadAllProgress? . addChildUnits ( resource. progress)
131+ }
136132 }
137133 }
138-
139- // Automatically dismisses the view if all of the resources have downloaded successfully.
140- if allResourcesAreDownloaded {
141- dismiss ( )
142- }
143134 }
144135}
145136
@@ -205,3 +196,12 @@ private struct GaugeProgressViewStyle: ProgressViewStyle {
205196 }
206197 }
207198}
199+
200+ private extension Progress {
201+ /// Adds a process object and its unit count as a suboperation of a progress tree.
202+ /// - Parameter progress: The progress instance to add to the progress tree.
203+ func addChildUnits( _ progress: Progress ) {
204+ addChild ( progress, withPendingUnitCount: progress. totalUnitCount)
205+ totalUnitCount += progress. totalUnitCount
206+ }
207+ }
0 commit comments