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
+