mirror of
https://github.com/neon443/NearFuture.git
synced 2026-03-11 06:49:12 +00:00
reset date button, simplified addEvent(), refactored EventListView, reindenting
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
A979F6142D270AF90094C0B3 /* NearFutureWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
A979F6182D2714310094C0B3 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Item.swift */; };
|
||||
A985104E2DB256430013D5FF /* iCloudSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985104D2DB256430013D5FF /* iCloudSettingsView.swift */; };
|
||||
A98510502DB263F00013D5FF /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985104F2DB263F00013D5FF /* EventListView.swift */; };
|
||||
A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -87,6 +88,7 @@
|
||||
A980FC302D920097006A778F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
A980FC372D93FB2B006A778F /* NearFutureTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NearFutureTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A985104D2DB256430013D5FF /* iCloudSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iCloudSettingsView.swift; sourceTree = "<group>"; };
|
||||
A985104F2DB263F00013D5FF /* EventListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventListView.swift; sourceTree = "<group>"; };
|
||||
A9C05E412D2805D7007DC497 /* NearFutureWidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NearFutureWidgetsExtension.entitlements; sourceTree = "<group>"; };
|
||||
A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@@ -150,6 +152,7 @@
|
||||
A979F57E2D26B1300094C0B3 /* EditEventView.swift */,
|
||||
A920C2B72D2401A300E4F9B1 /* AddEventView.swift */,
|
||||
A920C2C02D2403CA00E4F9B1 /* ContentView.swift */,
|
||||
A985104F2DB263F00013D5FF /* EventListView.swift */,
|
||||
A93BC0932D2B18A3002E8BBD /* StatsView.swift */,
|
||||
A920C2B42D2401A100E4F9B1 /* SettingsView.swift */,
|
||||
A985104D2DB256430013D5FF /* iCloudSettingsView.swift */,
|
||||
@@ -350,6 +353,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A920C2BB2D2401A400E4F9B1 /* AddEventView.swift in Sources */,
|
||||
A98510502DB263F00013D5FF /* EventListView.swift in Sources */,
|
||||
A920C2C12D2403CA00E4F9B1 /* ContentView.swift in Sources */,
|
||||
A920C2B82D2401A300E4F9B1 /* SettingsView.swift in Sources */,
|
||||
A920C28C2D24011400E4F9B1 /* Item.swift in Sources */,
|
||||
|
||||
@@ -16,7 +16,7 @@ struct AddEventView: View {
|
||||
@Binding var eventCompleteDesc: String
|
||||
@Binding var eventSymbol: String
|
||||
@Binding var eventColor: Color
|
||||
@Binding var eventDescription: String
|
||||
@Binding var eventNotes: String
|
||||
@Binding var eventDate: Date
|
||||
@Binding var eventTime: Bool
|
||||
@Binding var eventRecurrence: Event.RecurrenceType
|
||||
@@ -28,7 +28,7 @@ struct AddEventView: View {
|
||||
private enum Field {
|
||||
case Name, Description
|
||||
}
|
||||
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var body: some View {
|
||||
@@ -79,22 +79,33 @@ struct AddEventView: View {
|
||||
|
||||
// dscription
|
||||
ZStack {
|
||||
TextField("Event Description", text: $eventDescription)
|
||||
TextField("Event Description", text: $eventNotes)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
.padding(.trailing, eventDescription.isEmpty ? 0 : 30)
|
||||
.animation(.spring, value: eventDescription)
|
||||
.padding(.trailing, eventNotes.isEmpty ? 0 : 30)
|
||||
.animation(.spring, value: eventNotes)
|
||||
.focused($focusedField, equals: Field.Description)
|
||||
.submitLabel(.done)
|
||||
.onSubmit {
|
||||
focusedField = nil
|
||||
}
|
||||
MagicClearButton(text: $eventDescription)
|
||||
MagicClearButton(text: $eventNotes)
|
||||
}
|
||||
|
||||
|
||||
// date picker
|
||||
DatePicker("", selection: $eventDate, displayedComponents: .date)
|
||||
.datePickerStyle(WheelDatePickerStyle())
|
||||
HStack {
|
||||
DatePicker("", selection: $eventDate, displayedComponents: .date)
|
||||
.datePickerStyle(WheelDatePickerStyle())
|
||||
Button() {
|
||||
eventDate = Date()
|
||||
} label: {
|
||||
Image(systemName: "arrow.uturn.left")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
}
|
||||
.buttonStyle(BorderlessButtonStyle())
|
||||
.frame(width: 20)
|
||||
}
|
||||
|
||||
Toggle("Schedule a Time", isOn: $eventTime)
|
||||
if eventTime {
|
||||
@@ -124,15 +135,17 @@ struct AddEventView: View {
|
||||
if adding {
|
||||
Button {
|
||||
viewModel.addEvent(
|
||||
name: eventName,
|
||||
complete: eventComplete,
|
||||
completedDesc: eventCompleteDesc,
|
||||
symbol: eventSymbol,
|
||||
color: ColorCodable(eventColor),
|
||||
description: eventDescription,
|
||||
date: eventDate,
|
||||
time: eventTime,
|
||||
recurrence: eventRecurrence
|
||||
newEvent: Event(
|
||||
name: eventName,
|
||||
complete: eventComplete,
|
||||
completeDesc: eventCompleteDesc,
|
||||
symbol: eventSymbol,
|
||||
color: ColorCodable(eventColor),
|
||||
notes: eventNotes,
|
||||
date: eventDate,
|
||||
time: eventTime,
|
||||
recurrence: eventRecurrence
|
||||
)
|
||||
)
|
||||
resetAddEventView()
|
||||
} label: {
|
||||
@@ -171,16 +184,8 @@ struct AddEventView: View {
|
||||
//reset addeventView
|
||||
eventName = ""
|
||||
eventSymbol = "star"
|
||||
eventColor = [
|
||||
Color.red,
|
||||
Color.orange,
|
||||
Color.yellow,
|
||||
Color.green,
|
||||
Color.blue,
|
||||
Color.indigo,
|
||||
Color.purple
|
||||
].randomElement() ?? Color.red
|
||||
eventDescription = ""
|
||||
eventColor = randomColor()
|
||||
eventNotes = ""
|
||||
eventDate = Date()
|
||||
eventRecurrence = .none
|
||||
dismiss()
|
||||
@@ -215,7 +220,7 @@ struct MagicClearButton: View {
|
||||
eventCompleteDesc: .constant(""),
|
||||
eventSymbol: .constant("star"),
|
||||
eventColor: .constant(Color.red),
|
||||
eventDescription: .constant("A very special day"),
|
||||
eventNotes: .constant("A very special day"),
|
||||
eventDate: .constant(Date()),
|
||||
eventTime: .constant(true),
|
||||
eventRecurrence: .constant(.monthly),
|
||||
|
||||
@@ -18,16 +18,8 @@ struct ContentView: View {
|
||||
@State private var eventComplete = false
|
||||
@State private var eventCompleteDesc = ""
|
||||
@State private var eventSymbol = "star"
|
||||
@State private var eventColor: Color = [
|
||||
Color.red,
|
||||
Color.orange,
|
||||
Color.yellow,
|
||||
Color.green,
|
||||
Color.blue,
|
||||
Color.indigo,
|
||||
Color.purple
|
||||
].randomElement() ?? Color.red
|
||||
@State private var eventDescription = ""
|
||||
@State private var eventColor: Color = randomColor()
|
||||
@State private var eventNotes = ""
|
||||
@State private var eventDate = Date()
|
||||
@State private var eventTime = false
|
||||
@State private var eventRecurrence: Event.RecurrenceType = .none
|
||||
@@ -39,7 +31,7 @@ struct ContentView: View {
|
||||
} else {
|
||||
return viewModel.events.filter {
|
||||
$0.name.localizedCaseInsensitiveContains(searchInput) ||
|
||||
$0.description.localizedCaseInsensitiveContains(searchInput)
|
||||
$0.notes.localizedCaseInsensitiveContains(searchInput)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,7 +117,7 @@ struct ContentView: View {
|
||||
eventCompleteDesc: $eventCompleteDesc,
|
||||
eventSymbol: $eventSymbol,
|
||||
eventColor: $eventColor,
|
||||
eventDescription: $eventDescription,
|
||||
eventNotes: $eventNotes,
|
||||
eventDate: $eventDate,
|
||||
eventTime: $eventTime,
|
||||
eventRecurrence: $eventRecurrence,
|
||||
@@ -160,128 +152,6 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct EventListView: View {
|
||||
@ObservedObject var viewModel: EventViewModel
|
||||
@State var event: Event
|
||||
|
||||
var body: some View {
|
||||
NavigationLink() {
|
||||
EditEventView(
|
||||
viewModel: viewModel,
|
||||
event: $event
|
||||
)
|
||||
} label: {
|
||||
HStack {
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.frame(width: 5)
|
||||
.foregroundStyle(
|
||||
event.color.color.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
.padding(.leading, -10)
|
||||
.padding(.vertical, 5)
|
||||
.animation(.spring, value: event.complete)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image(systemName: event.symbol)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundStyle(
|
||||
event.color.color.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
.animation(.spring, value: event.complete)
|
||||
Text("\(event.name)")
|
||||
.font(.headline)
|
||||
.strikethrough(event.complete)
|
||||
// .foregroundStyle(
|
||||
// event.complete ? .gray : .primary
|
||||
// )
|
||||
.animation(.spring, value: event.complete)
|
||||
}
|
||||
if !event.description.isEmpty {
|
||||
Text(event.description)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Text(
|
||||
event.date.formatted(
|
||||
date: .long,
|
||||
time: event.time ? .standard : .omitted
|
||||
)
|
||||
)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(
|
||||
event.color.color.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
.animation(.spring, value: event.complete)
|
||||
if event.recurrence != .none {
|
||||
Text("Recurs \(event.recurrence.rawValue)")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(
|
||||
.primary.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
.animation(.spring, value: event.complete)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack {
|
||||
Text("\(daysUntilEvent(event.date, short: false))")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(
|
||||
event.color.color.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
.animation(.spring, value: event.complete)
|
||||
}
|
||||
Button() {
|
||||
withAnimation(.spring) {
|
||||
event.complete.toggle()
|
||||
}
|
||||
let eventToModify = viewModel.events.firstIndex() { currEvent in
|
||||
currEvent.id == event.id
|
||||
}
|
||||
if let eventToModify = eventToModify {
|
||||
viewModel.events[eventToModify] = event
|
||||
viewModel.saveEvents()
|
||||
viewModel.loadEvents()
|
||||
}
|
||||
} label: {
|
||||
if event.complete {
|
||||
ZStack {
|
||||
Circle()
|
||||
.foregroundStyle(.green)
|
||||
Image(systemName: "checkmark")
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.scaledToFit()
|
||||
.frame(width: 15)
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundStyle(event.color.color)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
.frame(maxWidth: 25, maxHeight: 25)
|
||||
.animation(.spring, value: event.complete)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SearchHelp: View {
|
||||
@Binding var searchInput: String
|
||||
@FocusState var focusedField: Field?
|
||||
@@ -294,7 +164,7 @@ struct SearchHelp: View {
|
||||
.padding(.trailing)
|
||||
Text("Can't find what you're looking for?")
|
||||
}
|
||||
Text("Tip: The Search bar searches event names and descriptions")
|
||||
Text("Tip: The Search bar searches event names and notes")
|
||||
Button() {
|
||||
searchInput = ""
|
||||
focusedField = nil
|
||||
@@ -311,22 +181,3 @@ struct SearchHelp: View {
|
||||
#Preview {
|
||||
ContentView()
|
||||
}
|
||||
|
||||
#Preview("EventListView") {
|
||||
EventListView(
|
||||
viewModel: EventViewModel(),
|
||||
event:
|
||||
Event(
|
||||
name: "event",
|
||||
complete: false,
|
||||
completeDesc: "dofajiof",
|
||||
symbol: "star",
|
||||
color: ColorCodable(.orange),
|
||||
description: "lksdjfakdflkasjlkjl",
|
||||
date: Date(),
|
||||
time: true,
|
||||
recurrence: .daily
|
||||
// )
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ struct EditEventView: View {
|
||||
@State private var eventCompleteDesc: String
|
||||
@State private var eventSymbol: String
|
||||
@State private var eventColor: Color
|
||||
@State private var eventDescription: String
|
||||
@State private var eventNotes: String
|
||||
@State private var eventDate: Date
|
||||
@State private var eventTime: Bool
|
||||
@State private var eventRecurrence: Event.RecurrenceType
|
||||
@@ -30,7 +30,7 @@ struct EditEventView: View {
|
||||
_eventCompleteDesc = State(initialValue: event.wrappedValue.completeDesc)
|
||||
_eventSymbol = State(initialValue: event.wrappedValue.symbol)
|
||||
_eventColor = State(initialValue: event.wrappedValue.color.color)
|
||||
_eventDescription = State(initialValue: event.wrappedValue.description)
|
||||
_eventNotes = State(initialValue: event.wrappedValue.notes)
|
||||
_eventDate = State(initialValue: event.wrappedValue.date)
|
||||
_eventTime = State(initialValue: event.wrappedValue.time)
|
||||
_eventRecurrence = State(initialValue: event.wrappedValue.recurrence)
|
||||
@@ -40,7 +40,7 @@ struct EditEventView: View {
|
||||
event.name = eventName
|
||||
event.symbol = eventSymbol
|
||||
event.color = ColorCodable(eventColor)
|
||||
event.description = eventDescription
|
||||
event.notes = eventNotes
|
||||
event.date = eventDate
|
||||
event.recurrence = eventRecurrence
|
||||
|
||||
@@ -57,32 +57,30 @@ struct EditEventView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
// NavigationStack {
|
||||
AddEventView(
|
||||
viewModel: viewModel,
|
||||
eventName: $eventName,
|
||||
eventComplete: $eventComplete,
|
||||
eventCompleteDesc: $eventCompleteDesc,
|
||||
eventSymbol: $eventSymbol,
|
||||
eventColor: $eventColor,
|
||||
eventDescription: $eventDescription,
|
||||
eventDate: $eventDate,
|
||||
eventTime: $eventTime,
|
||||
eventRecurrence: $eventRecurrence,
|
||||
adding: false //bc we editing existing event
|
||||
)
|
||||
.navigationTitle("Edit Event")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button() {
|
||||
saveEdits()
|
||||
} label: {
|
||||
Text("Done")
|
||||
}
|
||||
.disabled(eventName == "")
|
||||
AddEventView(
|
||||
viewModel: viewModel,
|
||||
eventName: $eventName,
|
||||
eventComplete: $eventComplete,
|
||||
eventCompleteDesc: $eventCompleteDesc,
|
||||
eventSymbol: $eventSymbol,
|
||||
eventColor: $eventColor,
|
||||
eventNotes: $eventNotes,
|
||||
eventDate: $eventDate,
|
||||
eventTime: $eventTime,
|
||||
eventRecurrence: $eventRecurrence,
|
||||
adding: false //bc we editing existing event
|
||||
)
|
||||
.navigationTitle("Edit Event")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button() {
|
||||
saveEdits()
|
||||
} label: {
|
||||
Text("Done")
|
||||
}
|
||||
.disabled(eventName == "")
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,17 +88,7 @@ struct EditEventView: View {
|
||||
EditEventView(
|
||||
viewModel: EventViewModel(),
|
||||
event: .constant(
|
||||
Event(
|
||||
name: "Birthday",
|
||||
complete: false,
|
||||
completeDesc: "",
|
||||
symbol: "gear",
|
||||
color: ColorCodable(.red),
|
||||
description: "an event",
|
||||
date: Date(),
|
||||
time: true,
|
||||
recurrence: .yearly
|
||||
)
|
||||
EventViewModel().example
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
136
NearFuture/EventListView.swift
Normal file
136
NearFuture/EventListView.swift
Normal file
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// EventListView.swift
|
||||
// NearFuture
|
||||
//
|
||||
// Created by neon443 on 18/04/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct EventListView: View {
|
||||
@ObservedObject var viewModel: EventViewModel
|
||||
@State var event: Event
|
||||
|
||||
var body: some View {
|
||||
NavigationLink() {
|
||||
EditEventView(
|
||||
viewModel: viewModel,
|
||||
event: $event
|
||||
)
|
||||
} label: {
|
||||
HStack {
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.frame(width: 5)
|
||||
.foregroundStyle(
|
||||
event.color.color.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
.padding(.leading, -10)
|
||||
.padding(.vertical, 5)
|
||||
.animation(.spring, value: event.complete)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image(systemName: event.symbol)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundStyle(
|
||||
event.color.color.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
.animation(.spring, value: event.complete)
|
||||
Text("\(event.name)")
|
||||
.font(.headline)
|
||||
.strikethrough(event.complete)
|
||||
.animation(.spring, value: event.complete)
|
||||
}
|
||||
if !event.notes.isEmpty {
|
||||
Text(event.notes)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Text(
|
||||
event.date.formatted(
|
||||
date: .long,
|
||||
time: event.time ? .standard : .omitted
|
||||
)
|
||||
)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(
|
||||
event.color.color.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
.animation(.spring, value: event.complete)
|
||||
if event.recurrence != .none {
|
||||
Text("Recurs \(event.recurrence.rawValue)")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(
|
||||
.primary.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
.animation(.spring, value: event.complete)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack {
|
||||
Text("\(daysUntilEvent(event.date, short: false))")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(
|
||||
event.color.color.opacity(
|
||||
event.complete ? 0.5 : 1
|
||||
)
|
||||
)
|
||||
.animation(.spring, value: event.complete)
|
||||
}
|
||||
Button() {
|
||||
withAnimation(.spring) {
|
||||
event.complete.toggle()
|
||||
}
|
||||
let eventToModify = viewModel.events.firstIndex() { currEvent in
|
||||
currEvent.id == event.id
|
||||
}
|
||||
if let eventToModify = eventToModify {
|
||||
viewModel.events[eventToModify] = event
|
||||
viewModel.saveEvents()
|
||||
viewModel.loadEvents()
|
||||
}
|
||||
} label: {
|
||||
if event.complete {
|
||||
ZStack {
|
||||
Circle()
|
||||
.foregroundStyle(.green)
|
||||
Image(systemName: "checkmark")
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.scaledToFit()
|
||||
.frame(width: 15)
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundStyle(event.color.color)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
.frame(maxWidth: 25, maxHeight: 25)
|
||||
.animation(.spring, value: event.complete)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("EventListView") {
|
||||
EventListView(
|
||||
viewModel: EventViewModel(),
|
||||
event:
|
||||
EventViewModel().example
|
||||
)
|
||||
}
|
||||
@@ -26,11 +26,11 @@ struct Event: Identifiable, Codable {
|
||||
var completeDesc: String
|
||||
var symbol: String
|
||||
var color: ColorCodable
|
||||
var description: String
|
||||
var notes: String
|
||||
var date: Date
|
||||
var time: Bool
|
||||
var recurrence: RecurrenceType
|
||||
|
||||
|
||||
enum RecurrenceType: String, Codable, CaseIterable {
|
||||
case none, daily, weekly, monthly, yearly
|
||||
}
|
||||
@@ -44,16 +44,16 @@ struct ColorCodable: Codable {
|
||||
//for the brainrot kids: alpha is the opacity/transparency of the color,
|
||||
//alpha == 0 completely transparent
|
||||
//alpha == 1 completely opaque
|
||||
|
||||
|
||||
var color: Color {
|
||||
Color(red: red, green: green, blue: blue, opacity: alpha)
|
||||
}
|
||||
|
||||
|
||||
init(_ color: Color) {
|
||||
let uiColor = UIColor(color)
|
||||
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
|
||||
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
|
||||
|
||||
|
||||
self.red = Double(r)
|
||||
self.green = Double(g)
|
||||
self.blue = Double(b)
|
||||
@@ -92,22 +92,34 @@ func daysUntilEvent(_ eventDate: Date, short: Bool, sepLines: Bool = false) -> S
|
||||
class EventViewModel: ObservableObject {
|
||||
@Published var events: [Event] = []
|
||||
@Published var icloudData: [Event] = []
|
||||
|
||||
|
||||
@Published var example: Event = Event(
|
||||
name: "event",
|
||||
complete: false,
|
||||
completeDesc: "dofajiof",
|
||||
symbol: "star",
|
||||
color: ColorCodable(.orange),
|
||||
notes: "lksdjfakdflkasjlkjl",
|
||||
date: Date(),
|
||||
time: true,
|
||||
recurrence: .daily
|
||||
)
|
||||
|
||||
@Published var lastSync: Date? = nil
|
||||
@Published var icloudEventCount: Int = 0
|
||||
@Published var localEventCount: Int = 0
|
||||
@Published var syncStatus: String = "Not Synced"
|
||||
|
||||
|
||||
init() {
|
||||
loadEvents()
|
||||
}
|
||||
|
||||
|
||||
//appgroup or regular userdefaults
|
||||
let appGroupUserDefaults = UserDefaults(suiteName: "group.com.neon443.NearFuture") ?? UserDefaults.standard
|
||||
|
||||
|
||||
//icloud store
|
||||
let icloudStore = NSUbiquitousKeyValueStore.default
|
||||
|
||||
|
||||
// load from icloud or local
|
||||
func loadEvents() {
|
||||
//load icloud 1st
|
||||
@@ -118,7 +130,7 @@ class EventViewModel: ObservableObject {
|
||||
self.events = decodedIcEvents
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if events.isEmpty, let savedData = appGroupUserDefaults.data(forKey: "events") {
|
||||
let decoder = JSONDecoder()
|
||||
if let decodedEvents = try? decoder.decode([Event].self, from: savedData) {
|
||||
@@ -127,77 +139,55 @@ class EventViewModel: ObservableObject {
|
||||
}
|
||||
updateSyncStatus()
|
||||
}
|
||||
|
||||
|
||||
// save to local and icloud
|
||||
func saveEvents() {
|
||||
let encoder = JSONEncoder()
|
||||
if let encoded = try? encoder.encode(events) {
|
||||
appGroupUserDefaults.set(encoded, forKey: "events")
|
||||
|
||||
|
||||
//sync
|
||||
icloudStore.set(encoded, forKey: "events")
|
||||
icloudStore.synchronize()
|
||||
|
||||
|
||||
updateSyncStatus()
|
||||
loadEvents()
|
||||
WidgetCenter.shared.reloadAllTimelines()//reload all widgets when saving events
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func updateSyncStatus() {
|
||||
lastSync = Date()
|
||||
icloudEventCount = icloudData.count
|
||||
localEventCount = events.count
|
||||
|
||||
|
||||
if icloudEventCount == localEventCount {
|
||||
syncStatus = "Successful"
|
||||
} else {
|
||||
syncStatus = "Pending"
|
||||
}
|
||||
}
|
||||
|
||||
func addEvent(
|
||||
name: String,
|
||||
complete: Bool,
|
||||
completedDesc: String,
|
||||
symbol: String,
|
||||
color: ColorCodable,
|
||||
description: String,
|
||||
date: Date,
|
||||
time: Bool,
|
||||
recurrence: Event.RecurrenceType
|
||||
) {
|
||||
let newEvent = Event(
|
||||
name: name,
|
||||
complete: complete,
|
||||
completeDesc: completedDesc,
|
||||
symbol: symbol,
|
||||
color: color,
|
||||
description: description,
|
||||
date: date,
|
||||
time: time,
|
||||
recurrence: recurrence
|
||||
)
|
||||
|
||||
func addEvent(newEvent: Event) {
|
||||
events.append(newEvent)
|
||||
saveEvents() //sync with icloud
|
||||
}
|
||||
|
||||
|
||||
func removeEvent(at index: IndexSet) {
|
||||
events.remove(atOffsets: index)
|
||||
saveEvents() //sync local and icl
|
||||
}
|
||||
|
||||
|
||||
func hasUbiquitousKeyValueStore() -> Bool {
|
||||
let icloud = NSUbiquitousKeyValueStore.default
|
||||
|
||||
|
||||
let key = "com.neon443.NearFuture.testkey"
|
||||
let value = "testValue"
|
||||
|
||||
|
||||
icloud.set(value, forKey: key)
|
||||
icloud.synchronize()
|
||||
|
||||
|
||||
if icloud.string(forKey: key) != nil {
|
||||
// print("has UbiquitousKeyValueStore")
|
||||
icloud.removeObject(forKey: key)
|
||||
icloud.synchronize()
|
||||
return true
|
||||
@@ -208,36 +198,36 @@ class EventViewModel: ObservableObject {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func sync() {
|
||||
NSUbiquitousKeyValueStore.default.synchronize()
|
||||
loadEvents()
|
||||
}
|
||||
|
||||
|
||||
func replaceLocalWithiCloudData() {
|
||||
icloudStore.synchronize()
|
||||
self.events = self.icloudData
|
||||
saveEvents()
|
||||
}
|
||||
|
||||
|
||||
func replaceiCloudWithLocalData() {
|
||||
icloudStore.synchronize()
|
||||
self.icloudData = self.events
|
||||
saveEvents()
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
@@ -250,26 +240,26 @@ class EventViewModel: ObservableObject {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func importEvents(_ imp: String) {
|
||||
guard let impData = imp.data(using: .utf8) else {
|
||||
print("Failed to convert string to data")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
self.events = decoded
|
||||
saveEvents()
|
||||
@@ -279,7 +269,7 @@ class EventViewModel: ObservableObject {
|
||||
print("Failed to decode events: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//MARK: Danger Zone
|
||||
func dangerClearLocalData() {
|
||||
UserDefaults.standard.removeObject(forKey: "events")
|
||||
@@ -287,29 +277,29 @@ class EventViewModel: ObservableObject {
|
||||
events.removeAll()
|
||||
updateSyncStatus()
|
||||
}
|
||||
|
||||
|
||||
func dangerCleariCloudData() {
|
||||
icloudStore.removeObject(forKey: "events")
|
||||
icloudStore.synchronize()
|
||||
icloudData.removeAll()
|
||||
updateSyncStatus()
|
||||
}
|
||||
|
||||
|
||||
func dangerResetLocalData() {
|
||||
let userDFDict = UserDefaults.standard.dictionaryRepresentation()
|
||||
for key in userDFDict.keys {
|
||||
UserDefaults.standard.removeObject(forKey: key)
|
||||
}
|
||||
|
||||
|
||||
let appGUSDDict = appGroupUserDefaults.dictionaryRepresentation()
|
||||
for key in appGUSDDict.keys {
|
||||
appGroupUserDefaults.removeObject(forKey: key)
|
||||
}
|
||||
|
||||
|
||||
events.removeAll()
|
||||
updateSyncStatus()
|
||||
}
|
||||
|
||||
|
||||
func dangerResetiCloud() {
|
||||
let icloudDict = icloudStore.dictionaryRepresentation
|
||||
for key in icloudDict.keys {
|
||||
@@ -324,7 +314,7 @@ class EventViewModel: ObservableObject {
|
||||
func describeOccurrence(date: Date, recurrence: Event.RecurrenceType) -> String {
|
||||
let dateString = date.formatted(date: .long, time: .omitted)
|
||||
let recurrenceDescription: String
|
||||
|
||||
|
||||
switch recurrence {
|
||||
case .none:
|
||||
recurrenceDescription = "Occurs once on"
|
||||
@@ -337,6 +327,26 @@ func describeOccurrence(date: Date, recurrence: Event.RecurrenceType) -> String
|
||||
case .yearly:
|
||||
recurrenceDescription = "Repeats every year from"
|
||||
}
|
||||
|
||||
|
||||
return "\(recurrenceDescription) \(dateString)"
|
||||
}
|
||||
|
||||
func randomRainbowColor() -> Color {
|
||||
return [
|
||||
Color.red,
|
||||
Color.orange,
|
||||
Color.yellow,
|
||||
Color.green,
|
||||
Color.blue,
|
||||
Color.indigo,
|
||||
Color.purple
|
||||
].randomElement()!
|
||||
}
|
||||
|
||||
func randomColor() -> Color {
|
||||
let r = Double.random(in: 0...1)
|
||||
let g = Double.random(in: 0...1)
|
||||
let b = Double.random(in: 0...1)
|
||||
let a = Double.random(in: 0...1)
|
||||
return Color(red: r, green: g, blue: b, opacity: a)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ struct StatsView: View {
|
||||
Text("\(pastEvents.count) past event\(pastEvents.count == 1 ? "" : "s")")
|
||||
.foregroundStyle(.gray)
|
||||
}
|
||||
|
||||
|
||||
Section("Events by Month") {
|
||||
let eventsByMonth = Dictionary(grouping: viewModel.events, by: { $0.date })
|
||||
ForEach(eventsByMonth.keys.sorted(), id: \.self) { month in
|
||||
|
||||
@@ -70,7 +70,7 @@ struct iCloudSettingsView: View {
|
||||
} message: {
|
||||
Text("This will replace Events stored in iCloud with Events stored locally.")
|
||||
}
|
||||
|
||||
|
||||
Button() {
|
||||
viewModel.sync()
|
||||
updateStatus()
|
||||
|
||||
@@ -112,12 +112,6 @@ struct EventWidgetView: View {
|
||||
}
|
||||
|
||||
if isLarge {
|
||||
if !event.description.isEmpty {
|
||||
Text(event.description)
|
||||
.font(.caption2)
|
||||
.foregroundColor(.gray)
|
||||
.padding(.top, -5)
|
||||
}
|
||||
Text(event.date.formatted(date: .long, time: .omitted))
|
||||
.font(.caption2)
|
||||
.foregroundColor(event.color.color)
|
||||
@@ -141,8 +135,6 @@ struct EventWidgetView: View {
|
||||
.foregroundColor(event.color.color)
|
||||
.padding(.trailing, -12)
|
||||
}
|
||||
} else {
|
||||
/*@START_MENU_TOKEN@*/EmptyView()/*@END_MENU_TOKEN@*/
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
@@ -150,7 +142,6 @@ struct EventWidgetView: View {
|
||||
let xMoreEvents = events.count - showedEventsNum
|
||||
Text("+\(xMoreEvents) more event\(xMoreEvents == 1 ? "" : "s")")
|
||||
.font(.caption2)
|
||||
// .foregroundStyle(.gray)
|
||||
.padding(.top, -5)
|
||||
.padding(.bottom, -15)
|
||||
}
|
||||
@@ -162,61 +153,10 @@ struct EventWidgetView: View {
|
||||
|
||||
struct Widget_Previews: PreviewProvider {
|
||||
static var events = [
|
||||
Event(
|
||||
name: "Event Name",
|
||||
complete: false,
|
||||
completeDesc: "",
|
||||
symbol: "gear",
|
||||
color: ColorCodable(.blue),
|
||||
description: "Event description",
|
||||
date: Date.distantFuture,
|
||||
time: false,
|
||||
recurrence: .yearly
|
||||
),
|
||||
Event(
|
||||
name: "distant past",
|
||||
complete: false,
|
||||
completeDesc: "",
|
||||
symbol: "star",
|
||||
color: ColorCodable(.orange),
|
||||
description: "description",
|
||||
date: Date.distantPast,
|
||||
time: false,
|
||||
recurrence: .daily
|
||||
),
|
||||
Event(
|
||||
name: "event",
|
||||
complete: false,
|
||||
completeDesc: "",
|
||||
symbol: "star",
|
||||
color: ColorCodable(.purple),
|
||||
description: "description",
|
||||
date: Date(),
|
||||
time: false,
|
||||
recurrence: .daily
|
||||
),
|
||||
Event(
|
||||
name: "An event",
|
||||
complete: false,
|
||||
completeDesc: "",
|
||||
symbol: "star",
|
||||
color: ColorCodable(.green),
|
||||
description: "description",
|
||||
date: Date(),
|
||||
time: false,
|
||||
recurrence: .daily
|
||||
),
|
||||
Event(
|
||||
name: "time event",
|
||||
complete: true,
|
||||
completeDesc: "",
|
||||
symbol: "clock",
|
||||
color: ColorCodable(.brown),
|
||||
description: "an event with a time",
|
||||
date: Date(),
|
||||
time: true,
|
||||
recurrence: .none
|
||||
)
|
||||
EventViewModel().example,
|
||||
EventViewModel().example,
|
||||
EventViewModel().example,
|
||||
EventViewModel().example
|
||||
]
|
||||
static var previews: some View {
|
||||
Group {
|
||||
|
||||
Reference in New Issue
Block a user