closed #113 - import / export
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
<key>Feels (macOS).xcscheme_^#shared#^_</key>
|
<key>Feels (macOS).xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>3</integer>
|
<integer>2</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>FeelsWidgetExtension.xcscheme_^#shared#^_</key>
|
<key>FeelsWidgetExtension.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
|||||||
@@ -7,12 +7,17 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import CloudKitSyncMonitor
|
import CloudKitSyncMonitor
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
@Environment(\.dismiss) var dismiss
|
@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 showSpecialThanks = false
|
||||||
@State private var showWhyBGMode = false
|
@State private var showWhyBGMode = false
|
||||||
@ObservedObject var syncMonitor = SyncMonitor.shared
|
@ObservedObject var syncMonitor = SyncMonitor.shared
|
||||||
@@ -44,6 +49,9 @@ struct SettingsView: View {
|
|||||||
if useCloudKit {
|
if useCloudKit {
|
||||||
cloudKitStatus
|
cloudKitStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportData
|
||||||
|
importData
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
@@ -62,6 +70,57 @@ struct SettingsView: View {
|
|||||||
theme.currentTheme.bg
|
theme.currentTheme.bg
|
||||||
.edgesIgnoringSafeArea(.all)
|
.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 {
|
private var closeButtonView: some View {
|
||||||
@@ -222,6 +281,34 @@ struct SettingsView: View {
|
|||||||
.cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
|
.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 {
|
private var randomIcons: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
theme.currentTheme.secondaryBGColor
|
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 {
|
struct SettingsView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
SettingsView()
|
SettingsView()
|
||||||
|
|||||||
Reference in New Issue
Block a user