Fix import/Export events

rewrote the daysTillEventt()
This commit is contained in:
neon443
2025-05-03 10:36:17 +01:00
parent 80b193f449
commit c97d37711c
7 changed files with 160 additions and 176 deletions

View File

@@ -16,6 +16,9 @@
A920C2BE2D24021A00E4F9B1 /* SFSymbolsPicker in Frameworks */ = {isa = PBXBuildFile; productRef = A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */; }; A920C2BE2D24021A00E4F9B1 /* SFSymbolsPicker in Frameworks */ = {isa = PBXBuildFile; productRef = A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */; };
A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2C02D2403CA00E4F9B1 /* ContentView.swift */; }; A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2C02D2403CA00E4F9B1 /* ContentView.swift */; };
A93BC0942D2B18A3002E8BBD /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A93BC0932D2B18A3002E8BBD /* StatsView.swift */; }; A93BC0942D2B18A3002E8BBD /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A93BC0932D2B18A3002E8BBD /* StatsView.swift */; };
A973B26C2DC551310028F8A2 /* ImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A973B26B2DC551310028F8A2 /* ImportView.swift */; };
A973B2702DC552EB0028F8A2 /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A973B26F2DC552EB0028F8A2 /* ExportView.swift */; };
A973B2712DC553050028F8A2 /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A973B26F2DC552EB0028F8A2 /* ExportView.swift */; };
A977CC922DBBB48000DED8C0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC912DBBB48000DED8C0 /* ArchiveView.swift */; }; A977CC922DBBB48000DED8C0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC912DBBB48000DED8C0 /* ArchiveView.swift */; };
A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC992DBD74FE00DED8C0 /* HelpView.swift */; }; A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC992DBD74FE00DED8C0 /* HelpView.swift */; };
A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F57E2D26B1300094C0B3 /* EditEventView.swift */; }; A979F57F2D26B1300094C0B3 /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F57E2D26B1300094C0B3 /* EditEventView.swift */; };
@@ -74,6 +77,8 @@
A920C2B72D2401A300E4F9B1 /* AddEventView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddEventView.swift; sourceTree = "<group>"; }; A920C2B72D2401A300E4F9B1 /* AddEventView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddEventView.swift; sourceTree = "<group>"; };
A920C2C02D2403CA00E4F9B1 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; A920C2C02D2403CA00E4F9B1 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
A93BC0932D2B18A3002E8BBD /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; }; A93BC0932D2B18A3002E8BBD /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
A973B26B2DC551310028F8A2 /* ImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportView.swift; sourceTree = "<group>"; };
A973B26F2DC552EB0028F8A2 /* ExportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportView.swift; sourceTree = "<group>"; };
A977CC912DBBB48000DED8C0 /* ArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveView.swift; sourceTree = "<group>"; }; A977CC912DBBB48000DED8C0 /* ArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveView.swift; sourceTree = "<group>"; };
A977CC992DBD74FE00DED8C0 /* HelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpView.swift; sourceTree = "<group>"; }; A977CC992DBD74FE00DED8C0 /* HelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpView.swift; sourceTree = "<group>"; };
A979F57E2D26B1300094C0B3 /* EditEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditEventView.swift; sourceTree = "<group>"; }; A979F57E2D26B1300094C0B3 /* EditEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditEventView.swift; sourceTree = "<group>"; };
@@ -165,6 +170,8 @@
A93BC0932D2B18A3002E8BBD /* StatsView.swift */, A93BC0932D2B18A3002E8BBD /* StatsView.swift */,
A977CC992DBD74FE00DED8C0 /* HelpView.swift */, A977CC992DBD74FE00DED8C0 /* HelpView.swift */,
A920C2B42D2401A100E4F9B1 /* SettingsView.swift */, A920C2B42D2401A100E4F9B1 /* SettingsView.swift */,
A973B26B2DC551310028F8A2 /* ImportView.swift */,
A973B26F2DC552EB0028F8A2 /* ExportView.swift */,
A985104D2DB256430013D5FF /* iCloudSettingsView.swift */, A985104D2DB256430013D5FF /* iCloudSettingsView.swift */,
A980FC302D920097006A778F /* Info.plist */, A980FC302D920097006A778F /* Info.plist */,
A920C28D2D24011A00E4F9B1 /* Assets.xcassets */, A920C28D2D24011A00E4F9B1 /* Assets.xcassets */,
@@ -361,8 +368,10 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A920C2BB2D2401A400E4F9B1 /* AddEventView.swift in Sources */, A920C2BB2D2401A400E4F9B1 /* AddEventView.swift in Sources */,
A973B26C2DC551310028F8A2 /* ImportView.swift in Sources */,
A98510502DB263F00013D5FF /* EventListView.swift in Sources */, A98510502DB263F00013D5FF /* EventListView.swift in Sources */,
A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */, A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */,
A973B2712DC553050028F8A2 /* ExportView.swift in Sources */,
A920C2B82D2401A300E4F9B1 /* SettingsView.swift in Sources */, A920C2B82D2401A300E4F9B1 /* SettingsView.swift in Sources */,
A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */, A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */,
A920C28C2D24011400E4F9B1 /* Item.swift in Sources */, A920C28C2D24011400E4F9B1 /* Item.swift in Sources */,
@@ -389,6 +398,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A973B2702DC552EB0028F8A2 /* ExportView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@@ -76,7 +76,9 @@ struct EventListView: View {
.foregroundStyle(.one) .foregroundStyle(.one)
} }
Button() { Button() {
event.complete.toggle() withAnimation {
event.complete.toggle()
}
let eventToModify = viewModel.events.firstIndex() { currEvent in let eventToModify = viewModel.events.firstIndex() { currEvent in
currEvent.id == event.id currEvent.id == event.id
} }

View File

@@ -0,0 +1,27 @@
//
// ExportView.swift
// NearFuture
//
// Created by neon443 on 02/05/2025.
//
import SwiftUI
struct ExportView: View {
@ObservedObject var viewModel: EventViewModel
var body: some View {
List {
Button() {
UIPasteboard.general.string = viewModel.exportEvents()
} label: {
Label("Copy Events", systemImage: "document.on.clipboard")
}
Text(viewModel.exportEvents())
.textSelection(.enabled)
}
}
}
#Preview {
ExportView(viewModel: dummyEventViewModel())
}

View File

@@ -0,0 +1,61 @@
//
// ImportView.swift
// NearFuture
//
// Created by neon443 on 02/05/2025.
//
import SwiftUI
struct ImportView: View {
@ObservedObject var viewModel: EventViewModel
@Binding var importStr: String
@State private var image: String = "clock.fill"
@State private var text: String = "Ready..."
@State private var fgColor: Color = .yellow
var body: some View {
List {
Section("Status") {
Label(text, systemImage: image)
.contentTransition(.numericText())
.foregroundStyle(fgColor)
}
TextField("", text: $importStr)
Button() {
do throws {
try viewModel.importEvents(importStr)
withAnimation {
image = "checkmark.circle.fill"
text = "Complete"
fgColor = .green
}
} catch importError.invalidB64 {
withAnimation {
image = "xmark.app.fill"
text = "Invalid base64 input."
fgColor = .red
}
} catch {
withAnimation {
image = "xmark.app.fill"
text = error.localizedDescription
fgColor = .red
}
}
} label: {
Label("Import", systemImage: "tray.and.arrow.down.fill")
}
.disabled(importStr.isEmpty)
}
}
}
#Preview {
ImportView(
viewModel: dummyEventViewModel(),
importStr: .constant("kljadfskljafdlkj;==")
)
}

View File

@@ -35,7 +35,22 @@ struct Event: Identifiable, Codable {
} }
} }
struct ColorCodable: Codable { struct ColorCodable: Codable, Equatable {
init(_ color: Color) {
let uiColor = UIColor(color)
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
self.red = Double(r)
self.green = Double(g)
self.blue = Double(b)
}
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
var red: Double var red: Double
var green: Double var green: Double
var blue: Double var blue: Double
@@ -57,103 +72,34 @@ struct ColorCodable: Codable {
self.blue = cc.blue self.blue = cc.blue
} }
} }
init(_ color: Color) {
let uiColor = UIColor(color)
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
self.red = Double(r)
self.green = Double(g)
self.blue = Double(b)
}
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
} }
func daysUntilEvent(_ eventDate: Date) -> (short: String, long: String) { func daysUntilEvent(_ eventDate: Date) -> (long: String, short: String) {
let calendar = Calendar.current let calendar = Calendar.current
let currentDate = Date() let now = Date()
let components = calendar.dateComponents([.day, .hour, .minute], from: currentDate, to: eventDate)
guard let days = components.day else {
return ("N/A", "N/A")
}
guard let hours = components.hour else {
return ("N/A", "N/A")
}
guard let minutes = components.minute else {
return ("N/A", "N/A")
}
enum RetUnit { let isToday = calendar.isDate(now, inSameDayAs: eventDate)
case days let components = calendar.dateComponents([.second, .day], from: now, to: eventDate)
case hours
case minutes guard !isToday else { return ("Today", "Today") }
} let secsComponents = eventDate.timeIntervalSinceNow
func ret(days: Int = 0, hours: Int = 0, minutes: Int = 0, unit: RetUnit) -> (String, String) { guard let daysCompontents = components.day else { return ("N/A", "N/A") }
var future: Bool = true let secs = Double(secsComponents)
var days = days var days = 0
var hours = hours var long = ""
var minutes = minutes var short = ""
if days < 0 || hours < 0 || minutes < 0 { if secs < 0 {
future = false
days.negate()
hours.negate()
minutes.negate()
} else {
future = true
}
switch unit {
case .days:
return (
"\(future ? "" : "-")\(days)d",
"\(days) day\(plu(days)) \(future ? "" : "ago")"
)
case .hours:
return (
"\(future ? "" : "-")\(hours)h",
"\(hours) hour\(plu(hours)) \(future ? "" : "ago")"
)
case .minutes:
return (
"\(future ? "" : "-")\(minutes)m",
"\(minutes) min\(plu(minutes)) \(future ? "" : "ago")"
)
}
}
switch eventDate > Date() {
case true:
//future
if days == 0 {
if hours == 0 {
//less than 1h
return ret(minutes: minutes, unit: .minutes)
} else {
//less than 24h
return ret(hours: hours, unit: .hours)
}
} else {
//grater than 24h
return ret(days: days, unit: .days)
}
case false:
//past //past
if days == 0 { days = Int(floor(secs/86400))
if hours == 0 { long = "\(-days) day\(plu(days)) ago"
//less than 1h short = "\(days)d"
return ret(minutes: minutes, unit: .minutes) } else {
} else { //future
//less than 24h days = Int(ceil(secs/86400))
return ret(hours: hours, unit: .hours) long = "\(days) day\(plu(days))"
} short = "\(days)d"
} else {
//grater than 24h
return ret(days: days, unit: .days)
}
} }
return (long, short)
} }
class EventViewModel: ObservableObject { class EventViewModel: ObservableObject {
@@ -297,57 +243,25 @@ class EventViewModel: ObservableObject {
saveEvents() saveEvents()
} }
func exportEvents() -> String? { func exportEvents() -> String {
let encoder = JSONEncoder() let encoder = JSONEncoder()
if let json = try? encoder.encode(self.events) {
// Custom date encoding strategy to handle date formatting return "\(json.base64EncodedString())"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
encoder.dateEncodingStrategy = .formatted(dateFormatter)
do {
// Encode the events array to JSON data
let encodedData = try encoder.encode(events)
// Convert the JSON data to a string
if let jsonString = String(data: encodedData, encoding: .utf8) {
return jsonString
} else {
print("Failed to convert encoded data to string")
return nil
}
} catch {
print("Failed to encode events: \(error.localizedDescription)")
return nil
} }
return ""
} }
func importEvents(_ imp: String) { func importEvents(_ imported: String) throws {
guard let impData = imp.data(using: .utf8) else { guard let data = Data(base64Encoded: imported) else {
print("Failed to convert string to data") throw importError.invalidB64
return
} }
// Create a JSONDecoder
let decoder = JSONDecoder() let decoder = JSONDecoder()
// Add a custom date formatter for decoding the date string
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" // Adjust this to the date format you're using
decoder.dateDecodingStrategy = .formatted(dateFormatter)
do { do {
// Attempt to decode the events from the provided data let decoded = try decoder.decode([Event].self, from: data)
let decoded = try decoder.decode([Event].self, from: impData)
print("Successfully decoded events: \(decoded)")
// Save and reload after importing events
self.events = decoded self.events = decoded
saveEvents() saveEvents()
loadEvents()
} catch { } catch {
// Print error if decoding fails throw error
print("Failed to decode events: \(error.localizedDescription)")
} }
} }
@@ -444,3 +358,7 @@ func plu(_ inp: Int) -> String {
if inp < 0 { input.negate() } if inp < 0 { input.negate() }
return "\(input == 1 ? "" : "s")" return "\(input == 1 ? "" : "s")"
} }
public enum importError: Error {
case invalidB64
}

View File

@@ -8,7 +8,7 @@
import SwiftUI import SwiftUI
struct SettingsView: View { struct SettingsView: View {
@State var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@State private var hasUbiquitous: Bool = false @State private var hasUbiquitous: Bool = false
@State private var lastSyncWasSuccessful: Bool = false @State private var lastSyncWasSuccessful: Bool = false
@@ -68,47 +68,17 @@ struct SettingsView: View {
viewModel.sync() viewModel.sync()
updateStatus() updateStatus()
} }
NavigationLink() { NavigationLink() {
NavigationStack() { ImportView(viewModel: viewModel, importStr: $importStr)
Button() {
UIPasteboard.general.string = "\(viewModel.exportEvents() ?? "")"
print(viewModel.exportEvents() as Any)
} label: {
Text("copy")
}
Text("\(viewModel.exportEvents() ?? "")")
}
} label: { } label: {
Image(systemName: "list.bullet.rectangle") Label("Import Events", systemImage: "tray.and.arrow.down.fill")
Text("Export events") .foregroundStyle(.one)
} }
NavigationLink() { NavigationLink() {
NavigationStack() { ExportView(viewModel: viewModel)
VStack {
TextEditor(text: $importStr)
.foregroundStyle(.foreground, .gray)
.background(.gray)
.frame(width: 200, height: 400)
.shadow(radius: 5)
Button() {
viewModel.importEvents(importStr)
} label: {
Text("import events")
}
.buttonStyle(BorderedProminentButtonStyle())
Button() {
if let pb = UIPasteboard.general.string {
print(pb)
}
} label: {
Text("print pb")
}
}
}
} label: { } label: {
Image(systemName: "square.and.arrow.down") Label("Export Events", systemImage: "square.and.arrow.up")
Text("Import events") .foregroundStyle(.one)
} }
Section("Tip") { Section("Tip") {
@@ -147,7 +117,3 @@ struct SettingsView: View {
#Preview { #Preview {
SettingsView(viewModel: dummyEventViewModel()) SettingsView(viewModel: dummyEventViewModel())
} }
func test() -> Void {
}

View File

@@ -191,6 +191,6 @@ struct iCloudSettingsView: View {
lastSyncWasNormalAgo: .constant(true), lastSyncWasNormalAgo: .constant(true),
localCountEqualToiCloud: .constant(true), localCountEqualToiCloud: .constant(true),
icloudCountEqualToLocal: .constant(true), icloudCountEqualToLocal: .constant(true),
updateStatus: test updateStatus: {}
) )
} }