Skip to content

Commit dff0db2

Browse files
authored
Support for data store export (GroutUI open-trackers#4)
1 parent f6591df commit dff0db2

10 files changed

Lines changed: 349 additions & 7 deletions

File tree

Gym Routine Tracker Plus.xcodeproj/project.pbxproj

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
4001ED0D29732D4500F165ED /* ExportSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4001ED0C29732D4500F165ED /* ExportSettings.swift */; };
11+
4001ED0F29732E1300F165ED /* ColorSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4001ED0E29732E1300F165ED /* ColorSettings.swift */; };
12+
401064D92973374700BFB00E /* ZipDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401064D82973374700BFB00E /* ZipDocument.swift */; };
1013
4011EECD2961C9B000A36D87 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 4011EECC2961C9B000A36D87 /* LICENSE */; };
1114
40245EF6296BE7D4007B5DAB /* ExerciseRunList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40245EF5296BE7D4007B5DAB /* ExerciseRunList.swift */; };
1215
402A5F2F296B7FE000A43DB3 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 402A5F2E296B7F7B00A43DB3 /* HistoryView.swift */; };
1316
402A5F32296B835B00A43DB3 /* Tabler in Frameworks */ = {isa = PBXBuildFile; productRef = 402A5F31296B835B00A43DB3 /* Tabler */; };
17+
403CA2CE2971F6660084785B /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 403CA2CD2971F6660084785B /* ZIPFoundation */; };
18+
403CA2D0297220460084785B /* Export-Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 403CA2CF29721E750084785B /* Export-Utils.swift */; };
19+
403CA2D22972829F0084785B /* PhoneSettingsForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 403CA2D12972829F0084785B /* PhoneSettingsForm.swift */; };
1420
408FACFE296CAC9300D02C9C /* RoutineRunList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 408FACFD296CABC400D02C9C /* RoutineRunList.swift */; };
1521
40B51E45294549DF0047377A /* GroutUI in Frameworks */ = {isa = PBXBuildFile; productRef = 40B51E44294549DF0047377A /* GroutUI */; };
1622
40CCF3F629454D2B007DDE69 /* GroutLib in Frameworks */ = {isa = PBXBuildFile; productRef = 40CCF3F529454D2B007DDE69 /* GroutLib */; };
@@ -23,10 +29,15 @@
2329
/* End PBXBuildFile section */
2430

2531
/* Begin PBXFileReference section */
32+
4001ED0C29732D4500F165ED /* ExportSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportSettings.swift; sourceTree = "<group>"; };
33+
4001ED0E29732E1300F165ED /* ColorSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorSettings.swift; sourceTree = "<group>"; };
34+
401064D82973374700BFB00E /* ZipDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZipDocument.swift; sourceTree = "<group>"; };
2635
4011EECC2961C9B000A36D87 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
2736
4011EECE2961C9BC00A36D87 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
2837
40245EF5296BE7D4007B5DAB /* ExerciseRunList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExerciseRunList.swift; sourceTree = "<group>"; };
2938
402A5F2E296B7F7B00A43DB3 /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = "<group>"; };
39+
403CA2CF29721E750084785B /* Export-Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Export-Utils.swift"; sourceTree = "<group>"; };
40+
403CA2D12972829F0084785B /* PhoneSettingsForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneSettingsForm.swift; sourceTree = "<group>"; };
3041
408FACFD296CABC400D02C9C /* RoutineRunList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutineRunList.swift; sourceTree = "<group>"; };
3142
40E347BA29452CF1003A19B9 /* Gym Routine Tracker Plus.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Gym Routine Tracker Plus.app"; sourceTree = BUILT_PRODUCTS_DIR; };
3243
40E347BD29452CF1003A19B9 /* PlusApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlusApp.swift; sourceTree = "<group>"; };
@@ -47,12 +58,36 @@
4758
40E347D329452E4B003A19B9 /* Compactor in Frameworks */,
4859
40EBE1CC29635306004B9189 /* GroutUI in Frameworks */,
4960
40CCF3F629454D2B007DDE69 /* GroutLib in Frameworks */,
61+
403CA2CE2971F6660084785B /* ZIPFoundation in Frameworks */,
5062
);
5163
runOnlyForDeploymentPostprocessing = 0;
5264
};
5365
/* End PBXFrameworksBuildPhase section */
5466

5567
/* Begin PBXGroup section */
68+
401064DA2973378500BFB00E /* Helper */ = {
69+
isa = PBXGroup;
70+
children = (
71+
401064D82973374700BFB00E /* ZipDocument.swift */,
72+
403CA2CF29721E750084785B /* Export-Utils.swift */,
73+
);
74+
path = Helper;
75+
sourceTree = "<group>";
76+
};
77+
401064DB2973378C00BFB00E /* Views */ = {
78+
isa = PBXGroup;
79+
children = (
80+
40E347BF29452CF1003A19B9 /* ContentView.swift */,
81+
4001ED0E29732E1300F165ED /* ColorSettings.swift */,
82+
40245EF5296BE7D4007B5DAB /* ExerciseRunList.swift */,
83+
4001ED0C29732D4500F165ED /* ExportSettings.swift */,
84+
402A5F2E296B7F7B00A43DB3 /* HistoryView.swift */,
85+
403CA2D12972829F0084785B /* PhoneSettingsForm.swift */,
86+
408FACFD296CABC400D02C9C /* RoutineRunList.swift */,
87+
);
88+
path = Views;
89+
sourceTree = "<group>";
90+
};
5691
40B51E41294549D90047377A /* Frameworks */ = {
5792
isa = PBXGroup;
5893
children = (
@@ -83,13 +118,11 @@
83118
isa = PBXGroup;
84119
children = (
85120
40E347BD29452CF1003A19B9 /* PlusApp.swift */,
86-
40E347BF29452CF1003A19B9 /* ContentView.swift */,
87-
402A5F2E296B7F7B00A43DB3 /* HistoryView.swift */,
88-
408FACFD296CABC400D02C9C /* RoutineRunList.swift */,
89-
40245EF5296BE7D4007B5DAB /* ExerciseRunList.swift */,
121+
401064DB2973378C00BFB00E /* Views */,
122+
401064DA2973378500BFB00E /* Helper */,
123+
40E347C329452CF1003A19B9 /* App.entitlements */,
90124
40E347C129452CF1003A19B9 /* Assets.xcassets */,
91125
40EBCFDE294BFB880082A172 /* Info.plist */,
92-
40E347C329452CF1003A19B9 /* App.entitlements */,
93126
40E347C429452CF1003A19B9 /* Preview Content */,
94127
);
95128
path = Sources;
@@ -125,6 +158,7 @@
125158
40CCF3F529454D2B007DDE69 /* GroutLib */,
126159
40EBE1CB29635306004B9189 /* GroutUI */,
127160
402A5F31296B835B00A43DB3 /* Tabler */,
161+
403CA2CD2971F6660084785B /* ZIPFoundation */,
128162
);
129163
productName = "Gym Routine Tracker Plus";
130164
productReference = 40E347BA29452CF1003A19B9 /* Gym Routine Tracker Plus.app */;
@@ -158,6 +192,7 @@
158192
40E347D129452E4B003A19B9 /* XCRemoteSwiftPackageReference "SwiftCompactor" */,
159193
40EBE1CA29635306004B9189 /* XCRemoteSwiftPackageReference "GroutUI" */,
160194
402A5F30296B835B00A43DB3 /* XCRemoteSwiftPackageReference "SwiftTabler" */,
195+
403CA2CC2971F6660084785B /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
161196
);
162197
productRefGroup = 40E347BB29452CF1003A19B9 /* Products */;
163198
projectDirPath = "";
@@ -187,6 +222,11 @@
187222
buildActionMask = 2147483647;
188223
files = (
189224
40E347C029452CF1003A19B9 /* ContentView.swift in Sources */,
225+
403CA2D22972829F0084785B /* PhoneSettingsForm.swift in Sources */,
226+
4001ED0F29732E1300F165ED /* ColorSettings.swift in Sources */,
227+
4001ED0D29732D4500F165ED /* ExportSettings.swift in Sources */,
228+
401064D92973374700BFB00E /* ZipDocument.swift in Sources */,
229+
403CA2D0297220460084785B /* Export-Utils.swift in Sources */,
190230
408FACFE296CAC9300D02C9C /* RoutineRunList.swift in Sources */,
191231
40245EF6296BE7D4007B5DAB /* ExerciseRunList.swift in Sources */,
192232
402A5F2F296B7FE000A43DB3 /* HistoryView.swift in Sources */,
@@ -424,6 +464,14 @@
424464
minimumVersion = 0.9.6;
425465
};
426466
};
467+
403CA2CC2971F6660084785B /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = {
468+
isa = XCRemoteSwiftPackageReference;
469+
repositoryURL = "https://github.com/weichsel/ZIPFoundation.git";
470+
requirement = {
471+
kind = upToNextMajorVersion;
472+
minimumVersion = 0.9.9;
473+
};
474+
};
427475
40E347D129452E4B003A19B9 /* XCRemoteSwiftPackageReference "SwiftCompactor" */ = {
428476
isa = XCRemoteSwiftPackageReference;
429477
repositoryURL = "https://github.com/openalloc/SwiftCompactor.git";
@@ -448,6 +496,11 @@
448496
package = 402A5F30296B835B00A43DB3 /* XCRemoteSwiftPackageReference "SwiftTabler" */;
449497
productName = Tabler;
450498
};
499+
403CA2CD2971F6660084785B /* ZIPFoundation */ = {
500+
isa = XCSwiftPackageProductDependency;
501+
package = 403CA2CC2971F6660084785B /* XCRemoteSwiftPackageReference "ZIPFoundation" */;
502+
productName = ZIPFoundation;
503+
};
451504
40B51E44294549DF0047377A /* GroutUI */ = {
452505
isa = XCSwiftPackageProductDependency;
453506
productName = GroutUI;

Sources/Helper/Export-Utils.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//
2+
// Export-Utils.swift
3+
//
4+
// Copyright 2023 OpenAlloc LLC
5+
//
6+
// This Source Code Form is subject to the terms of the Mozilla Public
7+
// License, v. 2.0. If a copy of the MPL was not distributed with this
8+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
9+
//
10+
11+
import CoreData
12+
import ZIPFoundation
13+
14+
import GroutLib
15+
16+
public func createZipArchive(_ context: NSManagedObjectContext,
17+
mainStore: NSPersistentStore,
18+
archiveStore: NSPersistentStore,
19+
format: ExportFormat = .CSV) throws -> Data?
20+
{
21+
guard let archive = Archive(accessMode: .create)
22+
else { throw DataError.archiveCreationFailure }
23+
24+
func makeDelimFile<T: NSFetchRequestResult & Encodable & MAttributable>(_: T.Type,
25+
inStore: NSPersistentStore?) throws
26+
{
27+
let request = makeRequest(T.self, inStore: inStore)
28+
let results = try context.fetch(request)
29+
let data = try exportData(results, format: format)
30+
let fileName = "\(T.fileNamePrefix).\(format.defaultFileExtension)"
31+
try archive.addEntry(with: fileName,
32+
type: .file,
33+
uncompressedSize: Int64(data.count),
34+
provider: { position, size -> Data in
35+
let range = Int(position) ..< Int(position) + size
36+
return data.subdata(in: range)
37+
})
38+
}
39+
40+
try makeDelimFile(Routine.self, inStore: mainStore)
41+
try makeDelimFile(Exercise.self, inStore: mainStore)
42+
43+
// NOT WORKING: Local function 'makeDelimFile(_:inStore:)' requires that 'NSManagedObject' conform to 'Encodable'
44+
// [ZRoutine.self, ZRoutineRun.self, ZExercise.self, ZExerciseRun.self].forEach {
45+
// try makeDelimFile($0, inStore: archiveStore)
46+
// }
47+
48+
try makeDelimFile(ZRoutine.self, inStore: archiveStore)
49+
try makeDelimFile(ZRoutineRun.self, inStore: archiveStore)
50+
try makeDelimFile(ZExercise.self, inStore: archiveStore)
51+
try makeDelimFile(ZExerciseRun.self, inStore: archiveStore)
52+
53+
return archive.data
54+
}

Sources/Helper/ZipDocument.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//
2+
// ZipDocument.swift
3+
//
4+
// Copyright 2022, 2023 OpenAlloc LLC
5+
//
6+
// This Source Code Form is subject to the terms of the Mozilla Public
7+
// License, v. 2.0. If a copy of the MPL was not distributed with this
8+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
9+
//
10+
11+
import SwiftUI
12+
import UniformTypeIdentifiers
13+
14+
/// Simple wrapper for Zip Archive data
15+
public struct ZipDocument: FileDocument {
16+
public static var readableContentTypes = [UTType.zip]
17+
public static var writableContentTypes = [UTType.zip]
18+
19+
private var data: Data
20+
21+
public init(data: Data) {
22+
self.data = data
23+
}
24+
25+
public init(configuration: ReadConfiguration) throws {
26+
data = configuration.file.regularFileContents ?? Data()
27+
}
28+
29+
public func fileWrapper(configuration _: WriteConfiguration) throws -> FileWrapper {
30+
FileWrapper(regularFileWithContents: data)
31+
}
32+
}

Sources/Views/ColorSettings.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// ColorSettings.swift
3+
//
4+
// Copyright 2023 OpenAlloc LLC
5+
//
6+
// This Source Code Form is subject to the terms of the Mozilla Public
7+
// License, v. 2.0. If a copy of the MPL was not distributed with this
8+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
9+
//
10+
11+
import SwiftUI
12+
13+
import GroutLib
14+
import GroutUI
15+
16+
struct ColorSettings: View {
17+
// MARK: - Locals
18+
19+
@AppStorage(colorSchemeModeKey) var colorSchemeMode: ColorSchemeMode = .automatic
20+
21+
// MARK: - Views
22+
23+
var body: some View {
24+
Section {
25+
Picker("Color", selection: $colorSchemeMode) {
26+
ForEach(ColorSchemeMode.allCases, id: \.self) { mode in
27+
Text(mode.description).tag(mode)
28+
}
29+
}
30+
.pickerStyle(SegmentedPickerStyle())
31+
} header: {
32+
Text("Color Scheme")
33+
.foregroundStyle(.tint)
34+
}
35+
}
36+
}
37+
38+
struct ColorSettings_Previews: PreviewProvider {
39+
static var previews: some View {
40+
Form {
41+
ColorSettings()
42+
}
43+
}
44+
}
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
99
//
1010

11-
import CoreData
11+
import os
1212
import SwiftUI
1313

1414
import GroutLib
@@ -28,6 +28,9 @@ struct ContentView: View {
2828
@SceneStorage("main-history-nav") private var historyNavData: Data?
2929
@SceneStorage("main-settings-nav") private var settingsNavData: Data?
3030

31+
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!,
32+
category: String(describing: ContentView.self))
33+
3134
var body: some View {
3235
TabView(selection: $selectedTab) {
3336
NavStack(name: "routines",
@@ -51,7 +54,7 @@ struct ContentView: View {
5154

5255
NavStack(name: "settings",
5356
navData: $settingsNavData) {
54-
SettingsForm()
57+
PhoneSettingsForm()
5558
}
5659
.tabItem {
5760
Label("Settings", systemImage: "gear")

0 commit comments

Comments
 (0)