8
8
import CocoaLumberjackSwift
9
9
import SwiftUI
10
10
import SwiftUIIntrospect
11
+ import ZIPFoundation
11
12
12
13
struct EjectListView : View {
13
14
@StateObject var searchViewModel = AppListSearchModel ( )
14
15
@StateObject var ejectList : EjectListModel
15
16
16
17
@State var quickLookExport : URL ?
17
18
@State var isDeletingAll = false
19
+ @State var isExportingAll = false
18
20
@State var isErrorOccurred : Bool = false
19
21
@State var lastError : Error ?
20
22
@@ -33,6 +35,23 @@ struct EjectListView: View {
33
35
34
36
var body : some View {
35
37
refreshableListView
38
+ . toolbar {
39
+ ToolbarItem ( placement: . topBarTrailing) {
40
+ Button {
41
+ exportAll ( )
42
+ } label: {
43
+ if isExportingAll {
44
+ ProgressView ( )
45
+ . progressViewStyle ( CircularProgressViewStyle ( ) )
46
+ . transition ( . opacity)
47
+ } else {
48
+ Label ( NSLocalizedString ( " Export All " , comment: " " ) , systemImage: " square.and.arrow.up " )
49
+ . transition ( . opacity)
50
+ }
51
+ }
52
+ }
53
+ }
54
+ . animation ( . easeOut, value: isExportingAll)
36
55
. quickLookPreview ( $quickLookExport)
37
56
}
38
57
@@ -256,8 +275,6 @@ struct EjectListView: View {
256
275
try injector. ejectAll ( )
257
276
} catch {
258
277
DispatchQueue . main. async {
259
- isDeletingAll = false
260
-
261
278
DDLogError ( " \( error) " , ddlog: InjectorV3 . main. logger)
262
279
263
280
lastError = error
@@ -271,6 +288,58 @@ struct EjectListView: View {
271
288
}
272
289
}
273
290
291
+ private func exportAll( ) {
292
+ let view = viewControllerHost. viewController?
293
+ . navigationController? . view
294
+
295
+ view? . isUserInteractionEnabled = false
296
+
297
+ isExportingAll = true
298
+
299
+ DispatchQueue . global ( qos: . userInteractive) . async {
300
+ defer {
301
+ DispatchQueue . main. async {
302
+ isExportingAll = false
303
+ view? . isUserInteractionEnabled = true
304
+ }
305
+ }
306
+
307
+ do {
308
+ try _exportAll ( )
309
+ } catch {
310
+ DispatchQueue . main. async {
311
+ DDLogError ( " \( error) " , ddlog: InjectorV3 . main. logger)
312
+
313
+ lastError = error
314
+ isErrorOccurred = true
315
+ }
316
+ }
317
+ }
318
+ }
319
+
320
+ private func _exportAll( ) throws {
321
+ let exportURL = InjectorV3 . temporaryRoot
322
+ . appendingPathComponent ( " Exports_ \( UUID ( ) . uuidString) " , isDirectory: true )
323
+
324
+ let fileMgr = FileManager . default
325
+ try fileMgr. createDirectory ( at: exportURL, withIntermediateDirectories: true )
326
+
327
+ for plugin in ejectList. filteredPlugIns {
328
+ let exportURL = exportURL. appendingPathComponent ( plugin. url. lastPathComponent)
329
+ try fileMgr. copyItem ( at: plugin. url, to: exportURL)
330
+ }
331
+
332
+ let zipURL = InjectorV3 . temporaryRoot
333
+ . appendingPathComponent (
334
+ " \( ejectList. app. name) _ \( ejectList. app. id) _ \( UUID ( ) . uuidString. components ( separatedBy: " - " ) . last ?? " " ) .zip " )
335
+
336
+ try fileMgr. zipItem ( at: exportURL, to: zipURL)
337
+
338
+ DispatchQueue . main. async {
339
+ quickLookExport = zipURL
340
+ }
341
+ }
342
+
274
343
@ViewBuilder
275
344
private func paddedHeaderFooterText( _ content: String ) -> some View {
276
345
if #available( iOS 15 , * ) {
0 commit comments