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 */; };
A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2C02D2403CA00E4F9B1 /* ContentView.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 */; };
A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A977CC992DBD74FE00DED8C0 /* HelpView.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>"; };
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>"; };
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>"; };
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>"; };
@@ -165,6 +170,8 @@
A93BC0932D2B18A3002E8BBD /* StatsView.swift */,
A977CC992DBD74FE00DED8C0 /* HelpView.swift */,
A920C2B42D2401A100E4F9B1 /* SettingsView.swift */,
A973B26B2DC551310028F8A2 /* ImportView.swift */,
A973B26F2DC552EB0028F8A2 /* ExportView.swift */,
A985104D2DB256430013D5FF /* iCloudSettingsView.swift */,
A980FC302D920097006A778F /* Info.plist */,
A920C28D2D24011A00E4F9B1 /* Assets.xcassets */,
@@ -361,8 +368,10 @@
buildActionMask = 2147483647;
files = (
A920C2BB2D2401A400E4F9B1 /* AddEventView.swift in Sources */,
A973B26C2DC551310028F8A2 /* ImportView.swift in Sources */,
A98510502DB263F00013D5FF /* EventListView.swift in Sources */,
A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */,
A973B2712DC553050028F8A2 /* ExportView.swift in Sources */,
A920C2B82D2401A300E4F9B1 /* SettingsView.swift in Sources */,
A977CC9A2DBD74FE00DED8C0 /* HelpView.swift in Sources */,
A920C28C2D24011400E4F9B1 /* Item.swift in Sources */,
@@ -389,6 +398,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A973B2702DC552EB0028F8A2 /* ExportView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -76,7 +76,9 @@ struct EventListView: View {
.foregroundStyle(.one)
}
Button() {
event.complete.toggle()
withAnimation {
event.complete.toggle()
}
let eventToModify = viewModel.events.firstIndex() { currEvent in
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 green: Double
var blue: Double
@@ -57,103 +72,34 @@ struct ColorCodable: Codable {
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 currentDate = 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")
}
let now = Date()
enum RetUnit {
case days
case hours
case minutes
}
func ret(days: Int = 0, hours: Int = 0, minutes: Int = 0, unit: RetUnit) -> (String, String) {
var future: Bool = true
var days = days
var hours = hours
var minutes = minutes
if days < 0 || hours < 0 || minutes < 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:
let isToday = calendar.isDate(now, inSameDayAs: eventDate)
let components = calendar.dateComponents([.second, .day], from: now, to: eventDate)
guard !isToday else { return ("Today", "Today") }
let secsComponents = eventDate.timeIntervalSinceNow
guard let daysCompontents = components.day else { return ("N/A", "N/A") }
let secs = Double(secsComponents)
var days = 0
var long = ""
var short = ""
if secs < 0 {
//past
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)
}
days = Int(floor(secs/86400))
long = "\(-days) day\(plu(days)) ago"
short = "\(days)d"
} else {
//future
days = Int(ceil(secs/86400))
long = "\(days) day\(plu(days))"
short = "\(days)d"
}
return (long, short)
}
class EventViewModel: ObservableObject {
@@ -297,57 +243,25 @@ class EventViewModel: ObservableObject {
saveEvents()
}
func exportEvents() -> String? {
func exportEvents() -> String {
let encoder = JSONEncoder()
// Custom date encoding strategy to handle date formatting
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
if let json = try? encoder.encode(self.events) {
return "\(json.base64EncodedString())"
}
return ""
}
func importEvents(_ imp: String) {
guard let impData = imp.data(using: .utf8) else {
print("Failed to convert string to data")
return
func importEvents(_ imported: String) throws {
guard let data = Data(base64Encoded: imported) else {
throw importError.invalidB64
}
// Create a 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 {
// Attempt to decode the events from the provided data
let decoded = try decoder.decode([Event].self, from: impData)
print("Successfully decoded events: \(decoded)")
// Save and reload after importing events
let decoded = try decoder.decode([Event].self, from: data)
self.events = decoded
saveEvents()
loadEvents()
} catch {
// Print error if decoding fails
print("Failed to decode events: \(error.localizedDescription)")
throw error
}
}
@@ -444,3 +358,7 @@ func plu(_ inp: Int) -> String {
if inp < 0 { input.negate() }
return "\(input == 1 ? "" : "s")"
}
public enum importError: Error {
case invalidB64
}

View File

@@ -8,7 +8,7 @@
import SwiftUI
struct SettingsView: View {
@State var viewModel: EventViewModel
@ObservedObject var viewModel: EventViewModel
@State private var hasUbiquitous: Bool = false
@State private var lastSyncWasSuccessful: Bool = false
@@ -68,47 +68,17 @@ struct SettingsView: View {
viewModel.sync()
updateStatus()
}
NavigationLink() {
NavigationStack() {
Button() {
UIPasteboard.general.string = "\(viewModel.exportEvents() ?? "")"
print(viewModel.exportEvents() as Any)
} label: {
Text("copy")
}
Text("\(viewModel.exportEvents() ?? "")")
}
ImportView(viewModel: viewModel, importStr: $importStr)
} label: {
Image(systemName: "list.bullet.rectangle")
Text("Export events")
Label("Import Events", systemImage: "tray.and.arrow.down.fill")
.foregroundStyle(.one)
}
NavigationLink() {
NavigationStack() {
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")
}
}
}
ExportView(viewModel: viewModel)
} label: {
Image(systemName: "square.and.arrow.down")
Text("Import events")
Label("Export Events", systemImage: "square.and.arrow.up")
.foregroundStyle(.one)
}
Section("Tip") {
@@ -147,7 +117,3 @@ struct SettingsView: View {
#Preview {
SettingsView(viewModel: dummyEventViewModel())
}
func test() -> Void {
}

View File

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