diff --git a/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist b/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist
index 78042d3..b3eaeb2 100644
--- a/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -12,7 +12,7 @@
Feels (macOS).xcscheme_^#shared#^_
orderHint
- 3
+ 2
FeelsWidgetExtension.xcscheme_^#shared#^_
diff --git a/Shared/views/SettingsView/SettingsView.swift b/Shared/views/SettingsView/SettingsView.swift
index c5b9989..ec43061 100644
--- a/Shared/views/SettingsView/SettingsView.swift
+++ b/Shared/views/SettingsView/SettingsView.swift
@@ -7,12 +7,17 @@
import SwiftUI
import CloudKitSyncMonitor
+import UniformTypeIdentifiers
struct SettingsView: View {
@Environment(\.dismiss) var dismiss
- @State private var showOnboarding = false
+ @State private var showingExporter = false
+ @State private var showingImporter = false
+ @State private var importContent = ""
+ @State private var showOnboarding = false
+
@State private var showSpecialThanks = false
@State private var showWhyBGMode = false
@ObservedObject var syncMonitor = SyncMonitor.shared
@@ -44,6 +49,9 @@ struct SettingsView: View {
if useCloudKit {
cloudKitStatus
}
+
+ exportData
+ importData
}
Spacer()
@@ -62,6 +70,57 @@ struct SettingsView: View {
theme.currentTheme.bg
.edgesIgnoringSafeArea(.all)
)
+ .fileExporter(isPresented: $showingExporter,
+ documents: [
+ TextFile()
+ ],
+ contentType: .plainText,
+ onCompletion: { result in
+ switch result {
+ case .success(let url):
+ print("Saved to \(url)")
+ case .failure(let error):
+ print(error.localizedDescription)
+ }
+ })
+ .fileImporter(isPresented: $showingImporter, allowedContentTypes: [.text],
+ allowsMultipleSelection: false) { result in
+ do {
+ guard let selectedFile: URL = try result.get().first else { return }
+ if selectedFile.startAccessingSecurityScopedResource() {
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss +0000"
+
+ guard let input = String(data: try Data(contentsOf: selectedFile), encoding: .utf8) else { return }
+ defer { selectedFile.stopAccessingSecurityScopedResource() }
+
+ var rows = input.components(separatedBy: "\n")
+ rows.removeFirst()
+ for row in rows {
+ let columns = row.components(separatedBy: ",")
+ if columns.count != 7 {
+ continue
+ }
+ let moodEntry = MoodEntry(context: PersistenceController.shared.viewContext)
+ moodEntry.canDelete = Bool(columns[0])!
+ moodEntry.canEdit = Bool(columns[1])!
+ moodEntry.entryType = Int16(columns[2])!
+ moodEntry.forDate = dateFormatter.date(from: columns[3])
+ moodEntry.moodValue = Int16(columns[4])!
+ moodEntry.timestamp = dateFormatter.date(from: columns[5])
+ moodEntry.weekDay = Int16(columns[6])!
+ try! PersistenceController.shared.viewContext.save()
+ }
+ PersistenceController.shared.saveAndRunDataListerners()
+ } else {
+ // Handle denied access
+ }
+ } catch {
+ // Handle failure.
+ print("Unable to read file contents")
+ print(error.localizedDescription)
+ }
+ }
}
private var closeButtonView: some View {
@@ -222,6 +281,34 @@ struct SettingsView: View {
.cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}
+ private var exportData: some View {
+ ZStack {
+ theme.currentTheme.secondaryBGColor
+ Button(action: {
+ showingExporter.toggle()
+ }, label: {
+ Text("Export")
+ })
+ .padding()
+ }
+ .fixedSize(horizontal: false, vertical: true)
+ .cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
+ }
+
+ private var importData: some View {
+ ZStack {
+ theme.currentTheme.secondaryBGColor
+ Button(action: {
+ showingImporter.toggle()
+ }, label: {
+ Text("Import")
+ })
+ .padding()
+ }
+ .fixedSize(horizontal: false, vertical: true)
+ .cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
+ }
+
private var randomIcons: some View {
ZStack {
theme.currentTheme.secondaryBGColor
@@ -354,6 +441,50 @@ struct SettingsView: View {
}
}
+struct TextFile: FileDocument {
+ // tell the system we support only plain text
+ static var readableContentTypes = [UTType.plainText]
+
+ // by default our document is empty
+ var text = ""
+
+ // a simple initializer that creates new, empty documents
+ init() {
+ let entries = PersistenceController.shared.getData(startDate: Date(timeIntervalSince1970: 0),
+ endDate: Date(),
+ includedDays: [])
+
+ var csvString = "canDelete,canEdit,entryType,forDate,moodValue,timestamp,weekDay\n"
+ for entry in entries {
+ let canDelete = entry.canDelete
+ let canEdit = entry.canEdit
+ let entryType = entry.entryType
+ let forDate = entry.forDate!
+ let moodValue = entry.moodValue
+ let timestamp = entry.timestamp!
+ let weekDay = entry.weekDay
+
+ let dataString = "\(canDelete),\(canEdit),\(entryType),\(String(describing: forDate)),\(moodValue),\(String(describing:timestamp)),\(weekDay)\n"
+ print("DATA: \(dataString)")
+ csvString = csvString.appending(dataString)
+ }
+ text = csvString
+ }
+
+ // this initializer loads data that has been saved previously
+ init(configuration: ReadConfiguration) throws {
+ if let data = configuration.file.regularFileContents {
+ text = String(decoding: data, as: UTF8.self)
+ }
+ }
+
+ // this will be called when the system wants to write our data to disk
+ func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
+ let data = Data(text.utf8)
+ return FileWrapper(regularFileWithContents: data)
+ }
+}
+
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView()