From 107d7379f4cf6876c2eee8b6148c42198528e484 Mon Sep 17 00:00:00 2001 From: neon443 <69979447+neon443@users.noreply.github.com> Date: Wed, 25 Dec 2024 20:29:46 +0000 Subject: [PATCH] =?UTF-8?q?ok=20i=20locked=20in=20and=20the=20basic=20app?= =?UTF-8?q?=20is=20done=20=F0=9F=8E=89=20-=20up=20next,=20accent=20colors?= =?UTF-8?q?=20for=20events=20and=20then:=20widgets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NearFuture.xcodeproj/project.pbxproj | 44 +++- .../xcshareddata/swiftpm/Package.resolved | 15 ++ .../xcschemes/NearFuture.xcscheme | 102 +++++++++ NearFuture/AddEventView.swift | 150 +++++++++++++ NearFuture/ContentView.swift | 209 ++++++++++++++---- NearFuture/Item.swift | 88 +++++++- NearFuture/NearFuture.entitlements | 10 +- 7 files changed, 554 insertions(+), 64 deletions(-) create mode 100644 NearFuture.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 NearFuture.xcodeproj/xcshareddata/xcschemes/NearFuture.xcscheme create mode 100644 NearFuture/AddEventView.swift diff --git a/NearFuture.xcodeproj/project.pbxproj b/NearFuture.xcodeproj/project.pbxproj index 98f3b7e..75e8591 100644 --- a/NearFuture.xcodeproj/project.pbxproj +++ b/NearFuture.xcodeproj/project.pbxproj @@ -6,6 +6,10 @@ objectVersion = 77; objects = { +/* Begin PBXBuildFile section */ + A91288592D1C7E3000912B3C /* SFSymbolsPicker in Frameworks */ = {isa = PBXBuildFile; productRef = A91288582D1C7E3000912B3C /* SFSymbolsPicker */; }; +/* End PBXBuildFile section */ + /* Begin PBXContainerItemProxy section */ A9B4BFDC2D1AE66700212CE2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -52,6 +56,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A91288592D1C7E3000912B3C /* SFSymbolsPicker in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -112,6 +117,7 @@ ); name = NearFuture; packageProductDependencies = ( + A91288582D1C7E3000912B3C /* SFSymbolsPicker */, ); productName = NearFuture; productReference = A9B4BFC82D1AE66000212CE2 /* NearFuture.app */; @@ -195,6 +201,9 @@ ); mainGroup = A9B4BFBF2D1AE66000212CE2; minimizedProjectReferenceProxies = 1; + packageReferences = ( + A91288572D1C7E3000912B3C /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */, + ); preferredProjectObjectVersion = 77; productRefGroup = A9B4BFC92D1AE66000212CE2 /* Products */; projectDirPath = ""; @@ -388,12 +397,14 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_CXX_STANDARD_LIBRARY_HARDENING = none; CODE_SIGN_ENTITLEMENTS = NearFuture/NearFuture.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"NearFuture/Preview Content\""; - DEVELOPMENT_TEAM = 8626DL2GW3; - ENABLE_HARDENED_RUNTIME = YES; + DEVELOPMENT_TEAM = 85Q9QG6DN7; + ENABLE_HARDENED_RUNTIME = NO; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; @@ -406,13 +417,14 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 18.2; + IPHONEOS_DEPLOYMENT_TARGET = 17; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = auto; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -428,11 +440,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = NearFuture/NearFuture.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"NearFuture/Preview Content\""; - DEVELOPMENT_TEAM = 8626DL2GW3; - ENABLE_HARDENED_RUNTIME = YES; + DEVELOPMENT_TEAM = 85Q9QG6DN7; + ENABLE_HARDENED_RUNTIME = NO; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; @@ -445,7 +458,7 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 18.2; + IPHONEOS_DEPLOYMENT_TARGET = 17; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; @@ -591,6 +604,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + A91288572D1C7E3000912B3C /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/alessiorubicini/SFSymbolsPicker"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.6; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + A91288582D1C7E3000912B3C /* SFSymbolsPicker */ = { + isa = XCSwiftPackageProductDependency; + package = A91288572D1C7E3000912B3C /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */; + productName = SFSymbolsPicker; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = A9B4BFC02D1AE66000212CE2 /* Project object */; } diff --git a/NearFuture.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/NearFuture.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..b5805b7 --- /dev/null +++ b/NearFuture.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "19df39f99b22f4ef95b73ed292ffb0c8d7694dd4c9db2b96ea73b091b7b1a026", + "pins" : [ + { + "identity" : "sfsymbolspicker", + "kind" : "remoteSourceControl", + "location" : "https://github.com/alessiorubicini/SFSymbolsPicker", + "state" : { + "revision" : "73c909b8a7fc77a30dd04208e33f759f8b52c4c8", + "version" : "1.0.6" + } + } + ], + "version" : 3 +} diff --git a/NearFuture.xcodeproj/xcshareddata/xcschemes/NearFuture.xcscheme b/NearFuture.xcodeproj/xcshareddata/xcschemes/NearFuture.xcscheme new file mode 100644 index 0000000..527730f --- /dev/null +++ b/NearFuture.xcodeproj/xcshareddata/xcschemes/NearFuture.xcscheme @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NearFuture/AddEventView.swift b/NearFuture/AddEventView.swift new file mode 100644 index 0000000..ce2729e --- /dev/null +++ b/NearFuture/AddEventView.swift @@ -0,0 +1,150 @@ +// +// AddEventView.swift +// NearFuture +// +// Created by Nihaal Sharma on 25/12/2024. +// + +import SwiftUI +import SFSymbolsPicker + +struct AddEventView: View { + @ObservedObject var viewModel: EventViewModel + @Binding var eventName: String + @Binding var eventSymbol: String + @Binding var eventDescription: String + @Binding var eventDate: Date + @Binding var eventRecurrence: Event.RecurrenceType + @Binding var isPresented: Bool + @State var isSymbolPickerPresented = false + + var body: some View { + Form { + Section(header: Text("Event Details").font(.headline).foregroundColor(.blue)) { + // name & symbol + HStack(spacing: 5) { + Button() { + isSymbolPickerPresented.toggle() + } label: { + Image(systemName: eventSymbol) + .resizable() + .scaledToFit() + .frame(width: 30, height: 30) + } + .buttonStyle(.bordered) + .sheet(isPresented: $isSymbolPickerPresented) { + SymbolsPicker( + selection: $eventSymbol, + title: "Choose a Symbol", + searchLabel: "Search...", + autoDismiss: true) + } + Divider() + ZStack { + TextField("Event Name", text: $eventName) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .padding(.trailing, eventName.isEmpty ? 0 : 30) + .animation(.spring, value: eventName) + MagicClearButton(text: $eventName) + } + } + + // dscription + ZStack { + TextField("Event Description", text: $eventDescription) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .padding(.trailing, eventDescription.isEmpty ? 0 : 30) + .animation(.spring, value: eventDescription) + MagicClearButton(text: $eventDescription) + } + + + // date picker + DatePicker("Event Date", selection: $eventDate, in: Date()..., displayedComponents: .date) + .datePickerStyle(WheelDatePickerStyle()) + + // re-ocurrence Picker + Picker("Recurrence", selection: $eventRecurrence) { + ForEach(Event.RecurrenceType.allCases, id: \.self) { recurrence in + Text(recurrence.rawValue.capitalized) + } + } + .pickerStyle(SegmentedPickerStyle()) + Text( + describeOccurrence( + date: eventDate, + recurrence: eventRecurrence + ) + ) + } + + // save button + Button { + viewModel.addEvent( + name: eventName, + symbol: eventSymbol, + description: eventDescription, + date: eventDate, + recurrence: eventRecurrence + ) + eventName = "" + eventSymbol = "star" + eventDescription = "" + eventDate = Date() + eventRecurrence = .none + isPresented = false + } label: { + Text("Save Event") + .font(.headline) + .cornerRadius(10) + .shadow(radius: 10) + .buttonStyle(BorderedProminentButtonStyle()) + } + .disabled(eventName.isEmpty || eventDescription.isEmpty) + if eventName.isEmpty && eventDescription.isEmpty { + Text("Give your event a name and description.") + } else if eventName.isEmpty { + Text("Give your event a name.") + } else if eventDescription.isEmpty { + Text("Give your event a description.") + } + } + } +} + +struct MagicClearButton: View { + @Binding var text: String + var body: some View { + HStack { + Spacer() + Button { + text = "" + } label: { + Image(systemName: "xmark.circle.fill") + .resizable() + .scaledToFit() + .frame(width: text.isEmpty ? 0 : 25) + .symbolRenderingMode(.hierarchical) + .padding(.trailing, -5) + .animation(.spring, value: text.isEmpty) + } + } + } +} + +struct AddEvent_Preview: PreviewProvider { + @State static var symbol = "star" + @State static var date = Date() + + static var previews: some View { + AddEventView( + viewModel: EventViewModel(), + eventName: .constant("Birthday"), + eventSymbol: $symbol, + eventDescription: .constant("A very special day"), + eventDate: $date, + eventRecurrence: .constant(.monthly), + isPresented: .constant(true) + ) + } +} diff --git a/NearFuture/ContentView.swift b/NearFuture/ContentView.swift index f1bf301..eebda99 100644 --- a/NearFuture/ContentView.swift +++ b/NearFuture/ContentView.swift @@ -8,59 +8,170 @@ import SwiftUI import SwiftData +//struct ContentView: View { +// @Environment(\.modelContext) private var modelContext +// @Query private var items: [Item] +// +// var body: some View { +// NavigationSplitView { +// List { +// ForEach(items) { item in +// NavigationLink { +// Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") +// } label: { +// Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) +// } +// } +// .onDelete(perform: deleteItems) +// } +//#if os(macOS) +// .navigationSplitViewColumnWidth(min: 180, ideal: 200) +//#endif +// .toolbar { +//#if os(iOS) +// ToolbarItem(placement: .navigationBarTrailing) { +// EditButton() +// } +//#endif +// ToolbarItem { +// Button(action: addItem) { +// Label("Add Item", systemImage: "plus") +// } +// } +// } +// } detail: { +// Text("Select an item") +// } +// } +// +// private func addItem() { +// withAnimation { +// let newItem = Item(timestamp: Date()) +// modelContext.insert(newItem) +// } +// } +// +// private func deleteItems(offsets: IndexSet) { +// withAnimation { +// for index in offsets { +// modelContext.delete(items[index]) +// } +// } +// } +//} +// +//#Preview { +// ContentView() +// .modelContainer(for: Item.self, inMemory: true) +//} + + struct ContentView: View { - @Environment(\.modelContext) private var modelContext - @Query private var items: [Item] + @StateObject private var viewModel = EventViewModel() + @State private var eventName = "" + @State private var eventSymbol = "star" + @State private var eventDescription = "" + @State private var eventDate = Date() + @State private var eventRecurrence: Event.RecurrenceType = .none + @State private var showingAddEventView = false + @State private var searchInput: String = "" + var filteredEvents: [Event] { + if searchInput.isEmpty { + return viewModel.events + } else { + return viewModel.events.filter { + $0.name.localizedCaseInsensitiveContains(searchInput) || + $0.description.localizedCaseInsensitiveContains(searchInput) + } + } + } + + var body: some View { + NavigationView { + VStack { + ZStack { + TextField( + "\(Image(systemName: "magnifyingglass")) Search", + text: $searchInput + ) + .padding(.trailing, searchInput.isEmpty ? 0 : 30) + .animation(.spring, value: searchInput) + .textFieldStyle(RoundedBorderTextFieldStyle()) + MagicClearButton(text: $searchInput) + } + .padding(.horizontal) + List { + ForEach(filteredEvents) { event in + EventListView(event: event) + } + .onDelete(perform: viewModel.removeEvent) + } + } + .navigationTitle("Near Future") +// .navigationTitle() { +// Text("hi") +// } + .navigationBarTitleDisplayMode(.inline) + .sheet(isPresented: $showingAddEventView) { + AddEventView( + viewModel: viewModel, + eventName: $eventName, + eventSymbol: $eventSymbol, + eventDescription: $eventDescription, + eventDate: $eventDate, + eventRecurrence: $eventRecurrence, + isPresented: $showingAddEventView + ) + } + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button(action: { + showingAddEventView.toggle() + }) { + Image(systemName: "plus.circle") + .resizable() + .scaledToFit() + } + } + } - var body: some View { - NavigationSplitView { - List { - ForEach(items) { item in - NavigationLink { - Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") - } label: { - Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) - } - } - .onDelete(perform: deleteItems) - } -#if os(macOS) - .navigationSplitViewColumnWidth(min: 180, ideal: 200) -#endif - .toolbar { -#if os(iOS) - ToolbarItem(placement: .navigationBarTrailing) { - EditButton() - } -#endif - ToolbarItem { - Button(action: addItem) { - Label("Add Item", systemImage: "plus") - } - } - } - } detail: { - Text("Select an item") - } - } + } + } +} - private func addItem() { - withAnimation { - let newItem = Item(timestamp: Date()) - modelContext.insert(newItem) - } - } - - private func deleteItems(offsets: IndexSet) { - withAnimation { - for index in offsets { - modelContext.delete(items[index]) - } - } - } +struct EventListView: View { + @State var event: Event + + var body: some View { + HStack { + VStack(alignment: .leading) { + HStack { + Image(systemName: event.symbol) + Text(event.name) + .font(.headline) + .padding(.bottom, 2) + } + Text(event.description) + .font(.subheadline) + .foregroundColor(.gray) + Text("Recurring: \(event.recurrence.rawValue.capitalized)") + .font(.subheadline) + .foregroundColor(.blue) + Text("In \(daysUntilEvent(event.date))") + .font(.subheadline) + .foregroundColor(.gray) + } + + Spacer() + + Text(event.date.formatted(date: .long, time: .omitted)) + .font(.subheadline) + .foregroundColor(.blue) + } + .padding(.vertical, 8) + } } #Preview { - ContentView() - .modelContainer(for: Item.self, inMemory: true) + ContentView() } diff --git a/NearFuture/Item.swift b/NearFuture/Item.swift index d5192b2..8a4671c 100644 --- a/NearFuture/Item.swift +++ b/NearFuture/Item.swift @@ -10,9 +10,87 @@ import SwiftData @Model final class Item { - var timestamp: Date - - init(timestamp: Date) { - self.timestamp = timestamp - } + var timestamp: Date + + init(timestamp: Date) { + self.timestamp = timestamp + } +} + +struct Event: Identifiable, Codable { + var id = UUID() + var name: String + var symbol: String + var description: String + var date: Date + var recurrence: RecurrenceType + + enum RecurrenceType: String, Codable, CaseIterable { + case none, daily, weekly, monthly, yearly + } +} + +func daysUntilEvent(_ eventDate: Date) -> String { + let calendar = Calendar.current + let currentDate = Date() + let components = calendar.dateComponents([.day], from: currentDate, to: eventDate) + guard let days = components.day else { return "N/A" } + return "\(days) days" +} + +class EventViewModel: ObservableObject { + @Published var events: [Event] = [] + + init() { + loadEvents() + } + + func loadEvents() { + if let savedData = UserDefaults.standard.data(forKey: "events") { + let decoder = JSONDecoder() + if let decodedEvents = try? decoder.decode([Event].self, from: savedData) { + self.events = decodedEvents + } + } + } + + func saveEvents() { + let encoder = JSONEncoder() + if let encoded = try? encoder.encode(events) { + UserDefaults.standard.set(encoded, forKey: "events") + } + } + + func addEvent(name: String, symbol: String, description: String, date: Date, recurrence: Event.RecurrenceType) { + let newEvent = Event( + name: name, + symbol: symbol, + description: description, + date: date, + recurrence: recurrence + ) + events.append(newEvent) + saveEvents() + } + + func removeEvent(at index: IndexSet) { + events.remove(atOffsets: index) + saveEvents() + } +} + +//TODO: make it better lol +func describeOccurrence(date: Date, recurrence: Event.RecurrenceType) -> String { + switch recurrence { + case .none: + return "Occurs once on \(date.formatted(date: .long, time: .omitted))" + case .daily: + return "Repeats every day from \(date.formatted(date: .long, time: .omitted))" + case .weekly: + return "Repeats every week from \(date.formatted(date: .long, time: .omitted))" + case .monthly: + return "Repeats every month from \(date.formatted(date: .long, time: .omitted))" + case .yearly: + return "Repeats every month from \(date.formatted(date: .long, time: .omitted))" + } } diff --git a/NearFuture/NearFuture.entitlements b/NearFuture/NearFuture.entitlements index f2ef3ae..9da580e 100644 --- a/NearFuture/NearFuture.entitlements +++ b/NearFuture/NearFuture.entitlements @@ -2,9 +2,11 @@ - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + get-task-allow +