Files
Reflect/Shared/views/SettingsView/SettingsView.swift
2022-03-11 11:50:39 -06:00

503 lines
20 KiB
Swift

//
// SettingsView.swift
// Feels
//
// Created by Trey Tartt on 1/8/22.
//
import SwiftUI
import CloudKitSyncMonitor
import UniformTypeIdentifiers
struct SettingsView: View {
@Environment(\.dismiss) var dismiss
@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
@AppStorage(UserDefaultsStore.Keys.useCloudKit.rawValue, store: GroupUserDefaults.groupDefaults) private var useCloudKit = false
@AppStorage(UserDefaultsStore.Keys.deleteEnable.rawValue, store: GroupUserDefaults.groupDefaults) private var deleteEnabled = true
@AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system
@AppStorage(UserDefaultsStore.Keys.textColor.rawValue, store: GroupUserDefaults.groupDefaults) private var textColor: Color = .black
var body: some View {
ScrollView {
VStack {
Group {
closeButtonView
.padding()
cloudKitEnable
canDelete
showOnboardingButton
whyBackgroundMode
specialThanksCell
}
Group {
addTestDataCell
clearDB
randomIcons
if useCloudKit {
cloudKitStatus
}
exportData
importData
}
Spacer()
Text("\(Bundle.main.appName) v \(Bundle.main.versionNumber) (Build \(Bundle.main.buildNumber))")
.font(.body)
}
.padding()
}.sheet(isPresented: $showOnboarding) {
OnboardingMain(onboardingData: UserDefaultsStore.getOnboarding(),
updateBoardingDataClosure: { onboardingData in
OnboardingDataDataManager.shared.updateOnboardingData(onboardingData: onboardingData)
showOnboarding = false
})
}
.background(
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 {
HStack{
Spacer()
Button(action: {
dismiss()
}, label: {
Text(String(localized: "settings_view_exit"))
.font(.body)
.foregroundColor(Color(UIColor.systemBlue))
})
}
}
private var specialThanksCell: some View {
ZStack {
theme.currentTheme.secondaryBGColor
VStack {
Button(action: {
withAnimation{
showSpecialThanks.toggle()
}
}, label: {
Text(String(localized: "settings_view_special_thanks_to_title"))
.foregroundColor(textColor)
})
.padding()
if showSpecialThanks {
Divider()
Link("Font Awesome", destination: URL(string: "https://fontawesome.com")!)
.accentColor(textColor)
.padding(.bottom)
Divider()
Link("Charts", destination: URL(string: "https://github.com/danielgindi/Charts")!)
.accentColor(textColor)
.padding(.bottom)
}
}
}
.frame(minWidth: 0, maxWidth: .infinity)
.cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}
private var addTestDataCell: some View {
ZStack {
theme.currentTheme.secondaryBGColor
Button(action: {
PersistenceController.shared.populateTestData()
}, label: {
Text("Add test data")
.foregroundColor(textColor)
})
.padding()
}
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}
private var clearDB: some View {
ZStack {
theme.currentTheme.secondaryBGColor
Button(action: {
PersistenceController.shared.clearDB()
}, label: {
Text("Clear DB")
.foregroundColor(textColor)
})
.padding()
}
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}
private var whyBackgroundMode: some View {
ZStack {
theme.currentTheme.secondaryBGColor
VStack {
Button(action: {
withAnimation{
showWhyBGMode.toggle()
}
}, label: {
Text(String(localized: "settings_view_why_bg_mode_title"))
.foregroundColor(textColor)
})
.padding()
if showWhyBGMode {
Text(String(localized: "settings_view_why_bg_mode_body"))
.foregroundColor(textColor)
.padding()
}
}
}
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}
private var showOnboardingButton: some View {
ZStack {
theme.currentTheme.secondaryBGColor
Button(action: {
showOnboarding.toggle()
}, label: {
Text(String(localized: "settings_view_show_onboarding"))
.foregroundColor(textColor)
})
.padding()
}
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}
private var cloudKitEnable: some View {
ZStack {
theme.currentTheme.secondaryBGColor
VStack {
Toggle(isOn: $useCloudKit, label: {
Text(String(localized: "settings_use_cloudkit_title"))
.foregroundColor(textColor)
})
.padding()
Text(String(localized: "settings_use_cloudkit_body"))
.foregroundColor(textColor)
}
.padding(.bottom)
}
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}
private var cloudKitStatus: some View {
ZStack {
theme.currentTheme.secondaryBGColor
VStack {
Image(systemName: syncMonitor.syncStateSummary.symbolName)
.foregroundColor(syncMonitor.syncStateSummary.symbolColor)
Text( syncMonitor.syncStateSummary.isBroken ? "cloudkit is broken" : "cloudkit is good")
.foregroundColor(textColor)
}
.padding()
}
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}
private var canDelete: some View {
ZStack {
theme.currentTheme.secondaryBGColor
VStack {
Toggle(String(localized: "settings_use_delete_enable"),
isOn: $deleteEnabled)
.foregroundColor(textColor)
.padding()
}
}
.fixedSize(horizontal: false, vertical: true)
.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
Button(action: {
var iconViews = [UIImage]()
// for _ in 0...300 {
// iconViews.append(
// IconView(iconViewModel: IconViewModel(
// backgroundImage: MoodImages.FontAwesome.icon(forMood: .great),
// bgColor: Color.random(),
// bgOverlayColor: Color.random(),
// centerImage: MoodImages.FontAwesome.icon(forMood: .great),
// innerColor: Color.random())
// ).asImage(size: CGSize(width: 1024, height: 1024)))
// }
iconViews.append(
IconView(iconViewModel: IconViewModel(
backgroundImage: MoodImages.FontAwesome.icon(forMood: .great),
bgColor: IconViewModel.great.bgColor,
bgOverlayColor: IconViewModel.great.bgOverlayColor,
centerImage: MoodImages.FontAwesome.icon(forMood: .great),
innerColor: IconViewModel.great.innerColor)
).asImage(size: CGSize(width: 1024, height: 1024))
)
iconViews.append(
IconView(iconViewModel: IconViewModel(
backgroundImage: MoodImages.FontAwesome.icon(forMood: .great),
bgColor: IconViewModel.good.bgColor,
bgOverlayColor: IconViewModel.good.bgOverlayColor,
centerImage: MoodImages.FontAwesome.icon(forMood: .great),
innerColor: IconViewModel.good.innerColor)
).asImage(size: CGSize(width: 1024, height: 1024))
)
iconViews.append(
IconView(iconViewModel: IconViewModel(
backgroundImage: MoodImages.FontAwesome.icon(forMood: .great),
bgColor: IconViewModel.average.bgColor,
bgOverlayColor: IconViewModel.average.bgOverlayColor,
centerImage: MoodImages.FontAwesome.icon(forMood: .great),
innerColor: IconViewModel.average.innerColor)
).asImage(size: CGSize(width: 1024, height: 1024))
)
iconViews.append(
IconView(iconViewModel: IconViewModel(
backgroundImage: MoodImages.FontAwesome.icon(forMood: .great),
bgColor: IconViewModel.bad.bgColor,
bgOverlayColor: IconViewModel.bad.bgOverlayColor,
centerImage: MoodImages.FontAwesome.icon(forMood: .great),
innerColor: IconViewModel.bad.innerColor)
).asImage(size: CGSize(width: 1024, height: 1024))
)
iconViews.append(
IconView(iconViewModel: IconViewModel(
backgroundImage: MoodImages.FontAwesome.icon(forMood: .great),
bgColor: IconViewModel.horrible.bgColor,
bgOverlayColor: IconViewModel.horrible.bgOverlayColor,
centerImage: MoodImages.FontAwesome.icon(forMood: .great),
innerColor: IconViewModel.horrible.innerColor)
).asImage(size: CGSize(width: 1024, height: 1024))
)
// iconViews.append(
// IconView(iconViewModel: IconViewModel(
// backgroundImage: MoodImages.FontAwesome.icon(forMood: .great),
// bgColor: Color(hex: "EF0CF3"),
// bgOverlayColor: Color(hex: "EF0CF3").darker(by: 40),
// centerImage: MoodImages.FontAwesome.icon(forMood: .great),
// innerColor: Color(hex: "EF0CF3"))
// ).asImage(size: CGSize(width: 1024, height: 1024))
// )
//
// iconViews.append(
// IconView(iconViewModel: IconViewModel(
// backgroundImage: MoodImages.FontAwesome.icon(forMood: .great),
// bgColor: Color(hex: "1AE5D6"),
// bgOverlayColor: Color(hex: "1AE5D6").darker(by: 40),
// centerImage: MoodImages.FontAwesome.icon(forMood: .great),
// innerColor: Color(hex: "1AE5D6"))
// ).asImage(size: CGSize(width: 1024, height: 1024))
// )
//
// iconViews.append(
// IconView(iconViewModel: IconViewModel(
// backgroundImage: MoodImages.FontAwesome.icon(forMood: .great),
// bgColor: Color(hex: "633EC1"),
// bgOverlayColor: Color(hex: "633EC1").darker(by: 40),
// centerImage: MoodImages.FontAwesome.icon(forMood: .great),
// innerColor: Color(hex: "633EC1"))
// ).asImage(size: CGSize(width: 1024, height: 1024))
// )
//
// iconViews.append(
// IconView(iconViewModel: IconViewModel(
// backgroundImage: MoodImages.FontAwesome.icon(forMood: .great),
// bgColor: Color(hex: "10F30C"),
// bgOverlayColor: Color(hex: "10F30C").darker(by: 40),
// centerImage: MoodImages.FontAwesome.icon(forMood: .great),
// innerColor: Color(hex: "10F30C"))
// ).asImage(size: CGSize(width: 1024, height: 1024))
// )
for (idx, image) in iconViews.enumerated() {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
var path = paths[0].appendingPathComponent("icons").path
path = path.appending("\(idx).jpg")
let url = URL(fileURLWithPath: path)
do {
try image.jpegData(compressionQuality: 1.0)?.write(to: url, options: .atomic)
print(url)
} catch {
print(error.localizedDescription)
}
}
}, label: {
Text("Create random icons")
.foregroundColor(textColor)
})
.padding()
}
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(10, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}
}
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()
SettingsView()
.preferredColorScheme(.dark)
}
}