31 Commits

Author SHA1 Message Date
neon443
f3501a9379 update redme 2025-07-17 09:07:31 +01:00
neon443
6f870ffa4f improve notification handling
fix crash on ocmpleting events
2025-06-25 20:20:38 +01:00
neon443
9841574d37 swift 6 migration complete 2025-06-23 20:10:54 +01:00
neon443
899304833c mb gng 2025-06-23 18:25:56 +01:00
neon443
4629d4f75f preparation for swift 6 2025-06-23 18:23:18 +01:00
neon443
be68c44ffe improvbe text field 2025-06-19 15:02:46 +01:00
neon443
3fe9077e69 rewrote haptic viewmodifiers again :cry
made textfield expand and allowed the enter button
fix crashes due to as! self
2025-06-19 13:44:00 +01:00
neon443
121dd79d54 fix crashiing on mac 2025-06-19 10:59:59 +01:00
neon443
01116c7fcb fix modifier uses
fix addeventview not compiling
2025-06-19 10:40:52 +01:00
neon443
3690a9e4d2 fix stuff for GM seed building
(cant use xcode beta to sumbit to appstore smh)
and bump version
2025-06-19 10:23:17 +01:00
neon443
c393404fec fix alert having like 2 cancel buttons 2025-06-19 09:48:07 +01:00
neon443
3ee22da036 made events update propery when changing on ios
update animations on adding and removing events
fix symbolpicker crash on fast scroll by making it not show all the symbols lol
might be ready to ship?
2025-06-19 09:37:15 +01:00
neon443
2b25ddf9b3 extract delete event logic
trying to add swipe actions again
2025-06-19 09:03:11 +01:00
neon443
01ff82181a crazy haptics on completeing events
new alert for importing events (my custom one was pretty shit)
2025-06-16 19:59:39 +01:00
neon443
b7ef7b4e19 add app type 2025-06-15 21:31:48 +01:00
neon443
7727f14ad4 Merge branch 'feat-mac' 2025-06-15 21:25:25 +01:00
neon443
e4107a2faa add success haptic and indentation 2025-06-15 21:24:15 +01:00
neon443
5ec16dd67a fix editing events on mac and ios
toolbar label()s instead of buton("label")s
fix event add/delete/tick animation
fix event complete progress on ios and mac and that it just spazzed out
browsing true for symbols browsing (not picking)
2025-06-15 21:18:58 +01:00
neon443
0cffe243eb completeevent button has a custo progress bar to work on ios aswell
extracted the com[lete button
2025-06-15 18:55:38 +01:00
neon443
6533fb85ed fix editing events 2025-06-15 15:41:37 +01:00
neon443
5c667679d5 fix "new edit event window" on mac not working
- it would save events with the same id and modify the previous one if u opened another one
add symbols to ios
date picker looks better mac
2025-06-15 15:34:08 +01:00
neon443
5dd25f1ede locked the freak in for this one -
can cancel events being completed!!!!!
added a cancel to symbolpicker
and made it dip after choosing
extracted completevent into viewmodel
eventlistview is pretty full rn -- need to cleana
added color picker ot addeventview wtf why wasnt it there
2025-06-14 21:32:11 +01:00
neon443
d80011ea27 remove my shitty uikit 2025-06-14 18:55:09 +01:00
Nihaal Sharma
d5580e52f5 Update README.md 2025-05-29 17:29:37 +01:00
Nihaal Sharma
84a7091e05 Update README.md 2025-05-29 17:22:53 +01:00
Nihaal Sharma
44b40894e4 Delete .github directory 2025-05-26 18:04:30 +01:00
Nihaal Sharma
e4842bd29a Update ios.yml 2025-05-26 18:02:37 +01:00
Nihaal Sharma
b378a831be Update ios.yml 2025-05-26 17:57:31 +01:00
Nihaal Sharma
2ff96a7093 Update ios.yml 2025-05-26 17:52:13 +01:00
Nihaal Sharma
4f5e31a6f3 Update ios.yml 2025-05-26 17:51:01 +01:00
Nihaal Sharma
266b27d817 Create ios.yml 2025-05-26 17:49:06 +01:00
25 changed files with 611 additions and 755 deletions

View File

@@ -12,6 +12,6 @@ TEAM_ID = 8JGND254B7
BUNDLE_ID = com.neon443.NearFuture
BUNDLE_ID_WIDGETS = com.neon443.NearFuture.widgets
GROUP_ID = group.NearFuture
VERSION = 5
VERSION = 5.0.1
NAME = Near Future
BUILD_NUMBER = 1
BUILD_NUMBER = 52

View File

@@ -32,23 +32,26 @@ struct NearFutureApp: App {
}
WindowGroup("Edit Event", for: Event.ID.self) { $eventID in
EditEventView(
viewModel: viewModel,
event: Binding(
get: {
viewModel.events.first(where: {$0.id == eventID}) ?? viewModel.template
},
set: { newValue in
if let eventIndex = viewModel.events.firstIndex(where: {
$0.id == eventID
}) {
viewModel.events[eventIndex] = newValue
}
viewModel.saveEvents()
}
if viewModel.events.first(where: {$0.id == eventID}) == nil {
AddEventView(
viewModel: viewModel
)
)
} else {
EditEventView(
viewModel: viewModel,
event: Binding(
get: {
viewModel.events.first(where: {$0.id == eventID}) ?? viewModel.template
},
set: { newValue in
viewModel.editEvent(newValue)
}
)
)
}
}
.defaultSize(width: 480, height: 550)
.windowIdealSize(.fitToContent)
.restorationBehavior(.disabled)
Window("About Near Future", id: "about") {

View File

@@ -19,6 +19,14 @@ struct ArchiveView: View {
ScrollView {
ForEach(filteredEvents) { event in
EventListView(viewModel: viewModel, event: event)
.contextMenu() {
Button(role: .destructive) {
viewModel.removeEvent(event)
} label: {
Label("Delete", systemImage: "trash")
.tint(.red )
}
}
}
}
.scrollContentBackground(.hidden)

View File

@@ -14,7 +14,7 @@ struct ContentView: View {
@State private var showAddEventView: Bool = false
@State private var symbolSearchInput: String = ""
var body: some View {
var body: some View {
NavigationSplitView {
List {
NavigationLink {
@@ -37,7 +37,8 @@ struct ContentView: View {
}
NavigationLink {
SymbolsPicker(
selection: $symbolSearchInput
selection: .constant(""),
browsing: true
)
} label: {
Image(systemName: "star.circle")
@@ -65,9 +66,7 @@ struct ContentView: View {
}
.sheet(isPresented: $showAddEventView) {
AddEventView(
viewModel: viewModel,
event: $viewModel.editableTemplate,
adding: true
viewModel: viewModel
)
.presentationSizing(.page)
}
@@ -75,11 +74,10 @@ struct ContentView: View {
Button() {
showAddEventView.toggle()
} label: {
Image(systemName: "plus")
Text("New")
Label("New", systemImage: "plus")
}
}
}
}
}
#Preview {

View File

@@ -1,180 +0,0 @@
//
// EventListView.swift
// MacNearFuture
//
// Created by neon443 on 21/05/2025.
//
import SwiftUI
struct EventListView: View {
@ObservedObject var viewModel: EventViewModel
@State var event: Event
@State var largeTick: Bool = false
@State var hovering: Bool = false
@Environment(\.openWindow) var openWindow
var body: some View {
ZStack {
Color.black.opacity(hovering ? 0.5 : 0.0)
HStack {
RoundedRectangle(cornerRadius: 5)
.frame(width: 7)
.foregroundStyle(
event.color.color.opacity(
event.complete ? 0.5 : 1
)
)
VStack(alignment: .leading) {
HStack {
Image(systemName: event.symbol)
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.shadow(radius: 5)
.foregroundStyle(
.one.opacity(
event.complete ? 0.5 : 1
)
)
Text("\(event.name)")
.bold()
.foregroundStyle(.one)
.strikethrough(event.complete)
.multilineTextAlignment(.leading)
}
if !event.notes.isEmpty {
Text(event.notes)
.foregroundStyle(.one.opacity(0.8))
.multilineTextAlignment(.leading)
}
Text(
event.date.formatted(
date: .long,
time: .shortened
)
)
.foregroundStyle(
.one.opacity(
event.complete ? 0.5 : 1
)
)
if event.recurrence != .none {
Text("Occurs \(event.recurrence.rawValue)")
.font(.subheadline)
.foregroundStyle(
.one.opacity(event.complete ? 0.5 : 1))
}
}
Spacer()
VStack {
Text("\(daysUntilEvent(event.date).long)")
.multilineTextAlignment(.trailing)
.foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
}
Button() {
withAnimation {
event.complete.toggle()
}
let eventToModify = viewModel.events.firstIndex() { currEvent in
currEvent.id == event.id
}
if let eventToModify = eventToModify {
viewModel.events[eventToModify] = event
viewModel.saveEvents()
}
} label: {
if event.complete {
ZStack {
Circle()
.foregroundStyle(.green)
Image(systemName: "checkmark")
.resizable()
.foregroundStyle(.white)
.scaledToFit()
.bold()
.frame(width: 15)
}
} else {
Image(systemName: "circle")
.resizable()
.scaledToFit()
.foregroundStyle(event.color.color)
}
}
.onHover() { hovering in
withAnimation {
largeTick.toggle()
}
}
.buttonStyle(.borderless)
.scaleEffect(largeTick ? 1.5 : 1)
.frame(maxWidth: 20)
.shadow(radius: 5)
.padding(.trailing, 15)
.animation(
.spring(response: 0.2, dampingFraction: 0.75, blendDuration: 2),
value: largeTick
)
}
.transition(.opacity)
.fixedSize(horizontal: false, vertical: true)
}
.onHover { isHovering in
withAnimation {
hovering.toggle()
}
}
.onTapGesture {
openWindow(value: event.id)
}
.contextMenu() {
Button(role: .destructive) {
let eventToModify = viewModel.events.firstIndex() { currEvent in
currEvent.id == event.id
}
if let eventToModify = eventToModify {
viewModel.events.remove(at: eventToModify)
viewModel.saveEvents()
}
} label: {
Label("Delete", systemImage: "trash")
}
}
}
}
#Preview("EventListView") {
let vm = dummyEventViewModel()
ZStack {
Color.black
VStack {
ForEach(0..<50) { _ in
Rectangle()
.foregroundStyle(randomColor().opacity(0.5))
.padding(-10)
}
.ignoresSafeArea(.all)
.blur(radius: 5)
}
VStack {
ForEach(vm.events) { event in
EventListView(
viewModel: vm,
event: event
)
}
}
.padding(.horizontal, 10)
}
}
#Preview {
EventListView(
viewModel: dummyEventViewModel(),
event: dummyEventViewModel().template
)
}

View File

@@ -34,6 +34,14 @@ struct HomeView: View {
if filteredEvents.contains(event) {
EventListView(viewModel: viewModel, event: event)
.id(event)
.contextMenu() {
Button(role: .destructive) {
viewModel.removeEvent(event)
} label: {
Label("Delete", systemImage: "trash")
.tint(.red)
}
}
}
}
}

View File

@@ -70,15 +70,12 @@
A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A979F60F2D270AF80094C0B3 /* Assets.xcassets */; };
A979F6142D270AF90094C0B3 /* NearFutureWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
A979F6182D2714310094C0B3 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Events.swift */; };
A98C20CB2DE730740008D61C /* EventListViewMac.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CA2DE730740008D61C /* EventListViewMac.swift */; };
A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CD2DE7308E0008D61C /* ArchiveView.swift */; };
A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CF2DE731BD0008D61C /* HomeView.swift */; };
A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20D32DE7339E0008D61C /* AboutView.swift */; };
A9C769A22DFDD1FC00082FFF /* SymbolsPicker.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A9C769A02DFDD1FC00082FFF /* SymbolsPicker.storyboard */; };
A9C769A32DFDD1FC00082FFF /* SymbolsPicker.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A9C769A02DFDD1FC00082FFF /* SymbolsPicker.storyboard */; };
A9C769A52DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */; };
A9C769A62DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */; };
A9C769A72DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */; };
A9BAC6882DFF242300EC8E44 /* CompleteEventButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */; };
A9BAC6892DFF242300EC8E44 /* CompleteEventButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */; };
A9D1C34D2DFE10FA00703C2D /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8402DCAABE00064DCA0 /* EventListView.swift */; };
A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */; };
/* End PBXBuildFile section */
@@ -159,12 +156,10 @@
A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsBundle.swift; sourceTree = "<group>"; };
A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsLiveActivity.swift; sourceTree = "<group>"; };
A979F60F2D270AF80094C0B3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
A98C20CA2DE730740008D61C /* EventListViewMac.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventListViewMac.swift; sourceTree = "<group>"; };
A98C20CD2DE7308E0008D61C /* ArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveView.swift; sourceTree = "<group>"; };
A98C20CF2DE731BD0008D61C /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
A98C20D32DE7339E0008D61C /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
A9C769A02DFDD1FC00082FFF /* SymbolsPicker.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SymbolsPicker.storyboard; sourceTree = "<group>"; };
A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolsPickerStoryboard.swift; sourceTree = "<group>"; };
A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompleteEventButton.swift; sourceTree = "<group>"; };
A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -222,7 +217,6 @@
children = (
A90D49332DDE0FAF00781124 /* ContentViewMac.swift */,
A98C20CF2DE731BD0008D61C /* HomeView.swift */,
A98C20CA2DE730740008D61C /* EventListViewMac.swift */,
A98C20CD2DE7308E0008D61C /* ArchiveView.swift */,
A91EF80F2DFCB66C00B8463D /* SettingsView.swift */,
);
@@ -236,6 +230,7 @@
A90D49512DDE2D0000781124 /* Extensions.swift */,
A90D49202DDE0A3B00781124 /* Model */,
A91EF80A2DFC910000B8463D /* ViewModifiers.swift */,
A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */,
);
path = Shared;
sourceTree = "<group>";
@@ -252,8 +247,6 @@
children = (
A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */,
A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */,
A9C769A02DFDD1FC00082FFF /* SymbolsPicker.storyboard */,
A9C769A42DFDD27500082FFF /* SymbolsPickerStoryboard.swift */,
);
path = SymbolsPicker;
sourceTree = "<group>";
@@ -516,7 +509,6 @@
files = (
A90D49442DDE1C7600781124 /* Tints.xcassets in Resources */,
A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */,
A9C769A22DFDD1FC00082FFF /* SymbolsPicker.storyboard in Resources */,
A949F8322DCAAA8A0064DCA0 /* NearFutureIcon.png in Resources */,
A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */,
);
@@ -527,7 +519,6 @@
buildActionMask = 2147483647;
files = (
A90D49462DDE1C7A00781124 /* Tints.xcassets in Resources */,
A9C769A32DFDD1FC00082FFF /* SymbolsPicker.storyboard in Resources */,
A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -539,11 +530,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A9C769A52DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */,
A91EF80E2DFC9A0C00B8463D /* WhatsNewView.swift in Sources */,
A91EF8192DFD77BF00B8463D /* SymbolsLoader.swift in Sources */,
A95E9EE42DFC77D400ED655F /* ImportView.swift in Sources */,
A98C20CB2DE730740008D61C /* EventListViewMac.swift in Sources */,
A9BAC6892DFF242300EC8E44 /* CompleteEventButton.swift in Sources */,
A91EF80C2DFC910000B8463D /* ViewModifiers.swift in Sources */,
A95E9ED92DFC742B00ED655F /* AccentIcon.swift in Sources */,
A91EF8102DFCB66C00B8463D /* SettingsView.swift in Sources */,
@@ -551,6 +541,7 @@
A95E9EE52DFC77E200ED655F /* ExportView.swift in Sources */,
A91EF8132DFCC87D00B8463D /* EditEventView.swift in Sources */,
A91EF8142DFCC87D00B8463D /* AddEventView.swift in Sources */,
A9D1C34D2DFE10FA00703C2D /* EventListView.swift in Sources */,
A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */,
A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */,
A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */,
@@ -588,8 +579,8 @@
A949F8512DCAABE00064DCA0 /* ExportView.swift in Sources */,
A95E9ED82DFC742B00ED655F /* AccentIcon.swift in Sources */,
A90D49532DDE2D0000781124 /* Extensions.swift in Sources */,
A9C769A62DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */,
A949F8522DCAABE00064DCA0 /* iCloudSettingsView.swift in Sources */,
A9BAC6882DFF242300EC8E44 /* CompleteEventButton.swift in Sources */,
A949F8532DCAABE00064DCA0 /* ImportView.swift in Sources */,
A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */,
A91EF81E2DFD796600B8463D /* SymbolsPicker.swift in Sources */,
@@ -609,7 +600,6 @@
A95E9EDA2DFC742B00ED655F /* AccentIcon.swift in Sources */,
A91EF80D2DFC910000B8463D /* ViewModifiers.swift in Sources */,
A91EF8182DFD77BF00B8463D /* SymbolsLoader.swift in Sources */,
A9C769A72DFDD27500082FFF /* SymbolsPickerStoryboard.swift in Sources */,
A91EF8072DFC8B8B00B8463D /* ColorCodable.swift in Sources */,
A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */,
A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */,
@@ -649,6 +639,7 @@
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -661,7 +652,7 @@
REGISTER_APP_GROUPS = YES;
SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
};
name = Debug;
};
@@ -680,6 +671,7 @@
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -692,7 +684,7 @@
REGISTER_APP_GROUPS = YES;
SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
};
name = Release;
};
@@ -845,6 +837,7 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Near Future";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@@ -873,7 +866,8 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -897,6 +891,7 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Near Future";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@@ -922,7 +917,8 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;

View File

@@ -23,18 +23,39 @@ struct ArchiveView: View {
} else {
ScrollView {
ForEach(filteredEvents) { event in
EventListView(viewModel: viewModel, event: event)
.transition(.moveAndFadeReversed)
.id(event.complete)
NavigationLink() {
EditEventView(
viewModel: viewModel,
event: Binding(
get: { event },
set: { newValue in
viewModel.editEvent(newValue)
}
)
)
} label: {
EventListView(viewModel: viewModel, event: event)
.id(event)
}
.transition(.moveAndFadeReversed)
.contextMenu() {
Button(role: .destructive) {
viewModel.removeEvent(event)
} label: {
Label("Delete", systemImage: "trash")
.tint(.red)
}
}
}
.padding(.horizontal)
}
.animation(.default, value: filteredEvents)
}
}
.transition(.opacity)
.scrollContentBackground(.hidden)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
ToolbarItem(placement: .primaryAction) {
AddEventButton(showingAddEventView: $showAddEvent)
}
}
@@ -43,9 +64,7 @@ struct ArchiveView: View {
}
.sheet(isPresented: $showAddEvent) {
AddEventView(
viewModel: viewModel,
event: $viewModel.editableTemplate,
adding: true
viewModel: viewModel
)
}
}

View File

@@ -12,6 +12,7 @@ import SwiftData
enum Tab {
case home
case archive
case symbols
case stats
case settings
}
@@ -33,6 +34,14 @@ struct ContentView: View {
Label("Archive", systemImage: "tray.full")
}
.tag(Tab.archive)
SymbolsPicker(
selection: .constant(""),
browsing: true
)
.tabItem {
Label("Symbols", systemImage: "star.circle")
}
.tag(Tab.symbols)
StatsView(viewModel: viewModel)
// SymbolsPickerStoryboardUIViewRepresentable()
.tabItem {

View File

@@ -10,14 +10,12 @@ import SwiftUI
struct AddEventView: View {
@ObservedObject var viewModel: EventViewModel
@Binding var event: Event
@State var event: Event = dummyEventViewModel().template
@State var adding: Bool
@State var adding: Bool = true
@State var showNeedsNameAlert: Bool = false
@State var isSymbolPickerPresented: Bool = false
@State private var bye: Bool = false
@FocusState private var focusedField: Field?
private enum Field {
case Name, Notes
@@ -25,6 +23,14 @@ struct AddEventView: View {
@Environment(\.dismiss) var dismiss
var isMac: Bool {
if #available(iOS 1, *) {
return false
} else {
return true
}
}
var body: some View {
ZStack {
if !adding {
@@ -64,17 +70,18 @@ struct AddEventView: View {
// dscription
ZStack {
TextField("Event Notes", text: $event.notes)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.trailing, event.notes.isEmpty ? 0 : 30)
.animation(.spring, value: event.notes)
.focused($focusedField, equals: Field.Notes)
.submitLabel(.done)
.onSubmit {
focusedField = nil
if event.notes.isEmpty {
HStack {
Text("Event Notes")
.opacity(0.5)
Spacer()
}
}
TextEditor(text: $event.notes)
.lineLimit(10)
}
ColorPicker("Event Color", selection: $event.color.colorBind)
// date picker
HStack {
@@ -82,6 +89,8 @@ struct AddEventView: View {
DatePicker("", selection: $event.date, displayedComponents: .date)
#if os(iOS)
.datePickerStyle(.wheel)
#else
.datePickerStyle(.graphical)
#endif
Spacer()
Button() {
@@ -100,6 +109,9 @@ struct AddEventView: View {
selection: $event.date,
displayedComponents: .hourAndMinute
)
#if os(macOS)
.datePickerStyle(.stepperField)
#endif
// re-ocurrence Picker
Picker("Recurrence", selection: $event.recurrence) {
@@ -138,16 +150,14 @@ struct AddEventView: View {
viewModel.addEvent(
newEvent: event
)
bye.toggle()
resetAddEventView()
#if canImport(UIKit)
UINotificationFeedbackGenerator().notificationOccurred(.success)
#endif
} label: {
Text("Save")
.font(.headline)
.cornerRadius(10)
.buttonStyle(BorderedProminentButtonStyle())
Label("Save", systemImage: "checkmark")
}
.tint(.accent)
.modifier(hapticSuccess(trigger: bye))
.disabled(event.name.isEmpty)
.onTapGesture {
if event.name.isEmpty {
@@ -164,9 +174,24 @@ struct AddEventView: View {
}
}
}
ToolbarItem(placement: .confirmationAction) {
if !adding {
Button() {
viewModel.editEvent(event)
dismiss()
#if canImport(UIKit)
UINotificationFeedbackGenerator().notificationOccurred(.success)
#endif
} label: {
Label("Done", systemImage: "checkmark")
}
.disabled(event.name == "")
}
}
}
.navigationTitle("Editing \(event.name) - Ne")
}
.scrollContentBackground(.hidden)
.scrollContentBackground(isMac ? .automatic : .hidden)
.presentationDragIndicator(.visible)
}
}
@@ -185,7 +210,6 @@ struct AddEventView: View {
.sheet(isPresented: .constant(true)) {
AddEventView(
viewModel: vm,
event: .constant(vm.template),
adding: true
)
}

View File

@@ -12,37 +12,13 @@ struct EditEventView: View {
@ObservedObject var viewModel: EventViewModel
@Binding var event: Event
fileprivate func saveEdits() {
//if there is an event in vM.events with the id of the event we r editing,
//firstindex - loops through the arr and finds first element where that events id matches editing event's id
if let index = viewModel.events.firstIndex(where: { xEvent in
xEvent.id == event.id
}) {
viewModel.events[index] = event
}
viewModel.saveEvents()
dismiss()
}
var body: some View {
AddEventView(
viewModel: viewModel,
event: $event,
event: event,
adding: false //bc we editing existing event
)
.navigationTitle("Edit Event")
.toolbar {
ToolbarItem(/*placement: .topBarTrailing*/) {
Button() {
saveEdits()
} label: {
Text("Done")
.bold()
}
.disabled(event.name == "")
}
}
}
}

View File

@@ -12,152 +12,171 @@ struct EventListView: View {
@ObservedObject var viewModel: EventViewModel
@State var event: Event
@Environment(\.openWindow) var openWindow
@State var hovering: Bool = false
#if canImport(AppKit)
var body: some View {
NavigationLink() {
EditEventView(
viewModel: viewModel,
event: $event
)
} label: {
ZStack {
HStack {
RoundedRectangle(cornerRadius: 5)
.frame(width: 7)
.foregroundStyle(
event.color.color.opacity(
event.complete ? 0.5 : 1
)
ZStack {
Color.black.opacity(hovering ? 0.5 : 0.0)
HStack {
RoundedRectangle(cornerRadius: 5)
.frame(width: 7)
.foregroundStyle(
event.color.color.opacity(
event.complete ? 0.5 : 1
)
VStack(alignment: .leading) {
HStack {
Image(systemName: event.symbol)
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.shadow(radius: 5)
.foregroundStyle(
.one.opacity(
event.complete ? 0.5 : 1
)
)
VStack(alignment: .leading) {
HStack {
Image(systemName: event.symbol)
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.shadow(radius: 5)
.foregroundStyle(
.one.opacity(
event.complete ? 0.5 : 1
)
Text("\(event.name)")
.font(.headline)
.foregroundStyle(.one)
.strikethrough(event.complete)
.multilineTextAlignment(.leading)
}
if !event.notes.isEmpty {
Text(event.notes)
.font(.subheadline)
.foregroundStyle(.one.opacity(0.8))
.multilineTextAlignment(.leading)
}
Text(
event.date.formatted(
date: .long,
time: .shortened
)
Text("\(event.name)")
.bold()
.foregroundStyle(.one)
.strikethrough(event.complete)
.multilineTextAlignment(.leading)
}
if !event.notes.isEmpty {
Text(event.notes)
.foregroundStyle(.one.opacity(0.8))
.multilineTextAlignment(.leading)
}
Text(
event.date.formatted(
date: .long,
time: .shortened
)
.font(.subheadline)
)
.foregroundStyle(
.one.opacity(
event.complete ? 0.5 : 1
)
)
if event.recurrence != .none {
Text("Occurs \(event.recurrence.rawValue)")
.font(.subheadline)
.foregroundStyle(
.one.opacity(event.complete ? 0.5 : 1))
}
}
Spacer()
VStack {
Text("\(daysUntilEvent(event.date).long)")
.multilineTextAlignment(.trailing)
.foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
}
CompleteEventButton(
viewModel: viewModel,
event: $event
)
}
.fixedSize(horizontal: false, vertical: true)
}
.onHover { isHovering in
withAnimation {
hovering.toggle()
}
}
.onTapGesture {
openWindow(value: event.id)
}
}
#else
var body: some View {
HStack {
RoundedRectangle(cornerRadius: 5)
.frame(width: 7)
.foregroundStyle(
event.color.color.opacity(
event.complete ? 0.5 : 1
)
)
VStack(alignment: .leading) {
HStack {
Image(systemName: event.symbol)
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.shadow(radius: 5)
.foregroundStyle(
.one.opacity(
event.complete ? 0.5 : 1
)
)
if event.recurrence != .none {
Text("Occurs \(event.recurrence.rawValue)")
.font(.subheadline)
.foregroundStyle(
.one.opacity(event.complete ? 0.5 : 1))
}
}
Spacer()
VStack {
Text("\(daysUntilEvent(event.date).long)")
.font(.subheadline)
.foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
.multilineTextAlignment(.trailing)
}
Button() {
withAnimation {
event.complete.toggle()
}
let eventToModify = viewModel.events.firstIndex() { currEvent in
currEvent.id == event.id
}
if let eventToModify = eventToModify {
viewModel.events[eventToModify] = event
viewModel.saveEvents()
}
} label: {
if event.complete {
ZStack {
Circle()
.foregroundStyle(.green)
Image(systemName: "checkmark")
.resizable()
.foregroundStyle(.white)
.scaledToFit()
.bold()
.frame(width: 15)
}
} else {
Image(systemName: "circle")
.resizable()
.scaledToFit()
.foregroundStyle(event.color.color)
}
}
.buttonStyle(.borderless)
.frame(maxWidth: 25, maxHeight: 25)
.shadow(radius: 5)
.padding(.trailing, 5)
.modifier(hapticSuccess(trigger: event.complete))
Text("\(event.name)")
.font(.headline)
.foregroundStyle(.one)
.strikethrough(event.complete)
.multilineTextAlignment(.leading)
}
.transition(.opacity)
.padding(.vertical, 5)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(
.one.opacity(0.5),
lineWidth: 1
)
if !event.notes.isEmpty {
Text(event.notes)
.font(.subheadline)
.foregroundStyle(.one.opacity(0.8))
.multilineTextAlignment(.leading)
}
Text(
event.date.formatted(
date: .long,
time: .shortened
)
)
.clipShape(
RoundedRectangle(cornerRadius: 10)
.font(.subheadline)
.foregroundStyle(
.one.opacity(
event.complete ? 0.5 : 1
)
)
.fixedSize(horizontal: false, vertical: true)
if event.recurrence != .none {
Text("Occurs \(event.recurrence.rawValue)")
.font(.subheadline)
.foregroundStyle(
.one.opacity(event.complete ? 0.5 : 1))
}
}
.contextMenu() {
Button(role: .destructive) {
let eventToModify = viewModel.events.firstIndex() { currEvent in
currEvent.id == event.id
}
if let eventToModify = eventToModify {
viewModel.events.remove(at: eventToModify)
viewModel.saveEvents()
}
} label: {
Label("Delete", systemImage: "trash")
}
Spacer()
VStack {
Text("\(daysUntilEvent(event.date).long)")
.font(.subheadline)
.foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
.multilineTextAlignment(.trailing)
}
CompleteEventButton(
viewModel: viewModel,
event: $event
)
}
.padding(.vertical, 5)
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(.one.opacity(0.5), lineWidth: 1)
)
.clipShape(RoundedRectangle(cornerRadius: 15))
.fixedSize(horizontal: false, vertical: true)
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
viewModel.removeEvent(event)
} label: {
Label("Delete", systemImage: "trash")
}
}
}
#endif
}
#Preview("EventListView") {
let vm = dummyEventViewModel()
ZStack {
Color.black
VStack {
ForEach(0..<50) { _ in
Rectangle()
.foregroundStyle(randomColor().opacity(0.5))
.padding(-10)
}
.ignoresSafeArea(.all)
.blur(radius: 5)
}
VStack {
ForEach(vm.events) { event in
EventListView(

View File

@@ -12,7 +12,6 @@ struct HomeView: View {
@ObservedObject var viewModel: EventViewModel
@ObservedObject var settingsModel: SettingsViewModel
@State private var event: Event = dummyEventViewModel().template
@State private var showingAddEventView: Bool = false
@State private var searchInput: String = ""
@Environment(\.colorScheme) var appearance
@@ -48,9 +47,29 @@ struct HomeView: View {
ScrollView {
// LazyVStack {
ForEach(filteredEvents) { event in
EventListView(viewModel: viewModel, event: event)
.transition(.moveAndFade)
.id(event.complete)
NavigationLink() {
EditEventView(
viewModel: viewModel,
event: Binding(
get: { event },
set: { newValue in
viewModel.editEvent(newValue)
}
)
)
} label: {
EventListView(viewModel: viewModel, event: event)
.id(event)
}
.transition(.moveAndFade)
.contextMenu() {
Button(role: .destructive) {
viewModel.removeEvent(event)
} label: {
Label("Delete", systemImage: "trash")
.tint(.red)
}
}
}
.padding(.horizontal)
// }
@@ -70,13 +89,11 @@ struct HomeView: View {
.modifier(navigationInlineLarge())
.sheet(isPresented: $showingAddEventView) {
AddEventView(
viewModel: viewModel,
event: $event,
adding: true //adding event
viewModel: viewModel
)
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
ToolbarItem(placement: .primaryAction) {
AddEventButton(showingAddEventView: $showingAddEventView)
}
}

View File

@@ -66,65 +66,19 @@ struct ImportView: View {
fgColor = .yellow
}
}
.blur(radius: showAlert ? 2 : 0)
Group {
Rectangle()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.foregroundStyle(replaceCurrentEvents ? .red.opacity(0.25) : .black.opacity(0.2))
.animation(.default, value: replaceCurrentEvents)
.ignoresSafeArea()
ZStack {
Rectangle()
.clipShape(RoundedRectangle(cornerRadius: 25))
VStack(alignment: .center) {
Text("Are you sure?")
.font(.largeTitle)
.bold()
.foregroundStyle(replaceCurrentEvents ? .red : .two)
.animation(.default, value: replaceCurrentEvents)
Text("This will replace your current events!")
.lineLimit(nil)
.multilineTextAlignment(.center)
.opacity(replaceCurrentEvents ? 1 : 0)
.animation(.default, value: replaceCurrentEvents)
.foregroundStyle(.two)
Toggle("Replace Events", isOn: $replaceCurrentEvents)
.foregroundStyle(.two)
Spacer()
HStack {
Button() {
withAnimation {
showAlert.toggle()
}
importEvents()
} label: {
Text("cancel")
.font(.title2)
.bold()
}
.buttonStyle(BorderedProminentButtonStyle())
Spacer()
Button() {
withAnimation {
showAlert.toggle()
}
importEvents()
} label: {
Text("yes")
.font(.title2)
.bold()
}
.buttonStyle(BorderedProminentButtonStyle())
}
.padding()
}
.padding()
.alert("Are you sure?", isPresented: $showAlert) {
Button(role: .destructive) {
importEvents()
} label: {
Text("Replace Events")
}
Button() {
importEvents()
} label: {
Text("Add to Events")
.foregroundStyle(.one)
}
.frame(maxWidth: 250, maxHeight: 250)
}
.opacity(showAlert ? 1 : 0)
}
}
}

View File

@@ -154,25 +154,25 @@ struct EventWidgetView: View {
.foregroundColor(event.date < Date() ? .red : .primary)
.padding(.trailing, -12)
} else {
Button(
intent: CompleteEvent(
eventID: IntentParameter(
title: LocalizedStringResource(
stringLiteral: event.id.uuidString
)
)
)
) {
if event.complete {
Circle()
.frame(width: 10)
.foregroundStyle(.green)
} else {
Circle()
.frame(width: 10)
.foregroundStyle(.gray)
}
}
// Button(
// intent: CompleteEvent(
// eventID: IntentParameter(
// title: LocalizedStringResource(
// stringLiteral: event.id.uuidString
// )
// )
// )
// ) {
// if event.complete {
// Circle()
// .frame(width: 10)
// .foregroundStyle(.green)
// } else {
// Circle()
// .frame(width: 10)
// .foregroundStyle(.gray)
// }
// }
Text(daysUntilEvent(event.date).long)
.font(.caption)
.multilineTextAlignment(.trailing)

View File

@@ -1,6 +1,32 @@
# NearFuture
<div align="center">
<br/>
<p>
<img src="https://github.com/neon443/NearFuture/blob/main/Resources/Assets.xcassets/AppIcon.appiconset/NearFutureIcon.png?raw=true" title="dockphobia" alt="dockphobia icon" width="100" />
</p>
<h3>Near Future</h3>
<p>
<a href="https://apps.apple.com/us/app/near-future-event-tracker/id6744963429">
download
<img alt="GitHub Release" src="https://img.shields.io/itunes/v/6744963429">
</a>
</p>
<p>
make your Dock scared of the mouse
<br/>
<a href="https://neon443.github.io">
made by neon443
</a>
</p>
<br/>
</div>
[App Store](https://apps.apple.com/us/app/near-future-event-tracker/id6744963429)
<div align="center">
<a href="https://shipwrecked.hackclub.com/?t=ghrm" target="_blank">
<img src="https://hc-cdn.hel1.your-objectstorage.com/s/v3/739361f1d440b17fc9e2f74e49fc185d86cbec14_badge.png"
alt="This project is part of Shipwrecked, the world's first hackathon on an island!"
style="width: 25%;">
</a>
</div>
**Near Future** is a SwiftUI App to help people track upcoming events - Holidays, Trips, Birthdays, Weddings, Anniversaries.

View File

@@ -0,0 +1,137 @@
//
// CompleteEventButton.swift
// NearFuture
//
// Created by neon443 on 15/06/2025.
//
import SwiftUI
struct CompleteEventButton: View {
@ObservedObject var viewModel: EventViewModel
@Binding var event: Event
@MainActor @State var timer: Timer?
@State var largeTick: Bool = false
@State var completeInProgress: Bool = false
@State var completeStartTime: Date = .now
@State var progress: Double = 0
private let completeDuration: TimeInterval = 3.0
var isMac: Bool {
#if canImport(AppKit)
return true
#else
return false
#endif
}
func startCompleting() {
#if canImport(UIKit)
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
#endif
withAnimation { completeInProgress = true }
completeStartTime = .now
progress = 0
timer = Timer(timeInterval: 0.02, repeats: true) { timer in
DispatchQueue.main.async {
guard completeInProgress else { return }
guard let timer = self.timer else { return }
guard timer.isValid else { return }
let elapsed = Date().timeIntervalSince(completeStartTime)
progress = min(1, elapsed)
#if canImport(UIKit)
UIImpactFeedbackGenerator(style: .light).impactOccurred()
#endif
if progress >= 1 {
withAnimation { completeInProgress = false }
viewModel.completeEvent(&event)
#if canImport(UIKit)
DispatchQueue.main.asyncAfter(deadline: .now()+0.02) {
UINotificationFeedbackGenerator().notificationOccurred(.success)
}
#endif
timer.invalidate()
progress = 0
}
}
}
RunLoop.main.add(timer!, forMode: .common)
}
var body: some View {
Group {
if completeInProgress {
ZStack {
CircularProgressView(progress: $progress)
Image(systemName: "xmark")
.bold()
}
.onTapGesture {
withAnimation { completeInProgress = false }
}
} else {
Image(systemName: event.complete ? "checkmark.circle.fill" : "circle")
.resizable().scaledToFit()
.foregroundStyle(event.complete ? .green : event.color.color)
.bold()
.onTapGesture {
startCompleting()
}
.onHover() { hovering in
withAnimation {
largeTick.toggle()
}
}
.scaleEffect(largeTick ? 1.5 : 1)
}
}
.frame(maxWidth: isMac ? 20 : 30)
.shadow(color: .one.opacity(0.2), radius: 2.5)
.padding(.trailing, isMac ? 15 : 5)
.transition(.scale)
.animation(.spring, value: completeInProgress)
.animation(
.spring(response: 0.2, dampingFraction: 0.75, blendDuration: 2),
value: largeTick
)
}
}
struct CircularProgressView: View {
@Binding var progress: Double
var body: some View {
ZStack {
Circle()
.stroke(
.two,
lineWidth: 5
)
Circle()
.trim(from: 0, to: progress)
.stroke(
.one,
lineWidth: 5
// style: StrokeStyle(
// lineWidth: 5,
// lineCap: .round
// )
)
.rotationEffect(.degrees(-90))
}
}
}
#Preview {
CompleteEventButton(
viewModel: dummyEventViewModel(),
event: .constant(dummyEventViewModel().example)
)
.scaleEffect(5)
}
#Preview {
CircularProgressView(progress: .constant(0.5))
}

View File

@@ -22,14 +22,14 @@ extension View {
extension AnyTransition {
static var moveAndFade: AnyTransition {
.asymmetric(
insertion: .move(edge: .leading),
insertion: .opacity,
removal: .move(edge: .trailing)
)
.combined(with: .opacity)
}
static var moveAndFadeReversed: AnyTransition {
.asymmetric(
insertion: .move(edge: .trailing),
insertion: .opacity,
removal: .move(edge: .leading)
)
.combined(with: .opacity)

View File

@@ -160,20 +160,24 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
eventUUIDs.remove(at: remove)
}
let components = getDateComponents(events[index].date)
//check the notif matches event details
if req.content.title == events[index].name,
req.content.subtitle == events[index].notes,
req.trigger == UNCalendarNotificationTrigger(dateMatching: components, repeats: false) {
//if it does, make sure the notif delets if u complete the veent
if events[index].complete {
//if it does, make sure the notif delets if u complete the veent or in the past
if events[index].complete || events[index].date > .now {
cancelNotif(req.identifier)
} else {
//dont cancel the notif
}
} else {
//reschedult it because the event details have changed
cancelNotif(req.identifier)
scheduleEventNotif(events[index])
}
} else {
//cancel if the event is deleted
//cancel notif if the event is deleted (doesnt exist/cannot be matched)
cancelNotif(req.identifier)
}
}
@@ -182,6 +186,14 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
scheduleEventNotif(event)
}
}
Task {
try? await UNUserNotificationCenter.current().setBadgeCount(await getNotifs().count)
}
print(eventUUIDs.count)
print(events.count(where: {!$0.complete && $0.date < .now}))
print(events.count(where: {!$0.complete && $0.date > .now}))
print(events.count(where: {!$0.complete}))
print(events.count(where: {$0.complete}))
}
// save to local and icloud
@@ -222,8 +234,31 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
saveEvents() //sync with icloud
}
func removeEvent(at index: IndexSet) {
events.remove(atOffsets: index)
func editEvent(_ editedEvent: Event) {
if let index = events.firstIndex(where: { editedEvent.id == $0.id }) {
self.events[index] = editedEvent
saveEvents()
}
}
func completeEvent(_ event: inout Event) {
withAnimation { event.complete.toggle() }
let eventToModify = self.events.firstIndex() { currEvent in
currEvent.id == event.id
}
if let eventToModify = eventToModify {
self.events[eventToModify] = event
self.saveEvents()
}
}
func removeEvent(_ eventToRemove: Event) {
let eventToModify = self.events.firstIndex() { currEvent in
currEvent.id == eventToRemove.id
}
if let eventToModify = eventToModify {
self.events.remove(at: eventToModify)
}
saveEvents() //sync local and icl
}
@@ -472,6 +507,7 @@ func getBuildID() -> String {
return "\(build)"
}
@MainActor
func getDevice() -> (sf: String, label: String) {
#if canImport(UIKit)
let asi = ProcessInfo().isiOSAppOnMac
@@ -489,57 +525,3 @@ func getDevice() -> (sf: String, label: String) {
return (sf: "desktopcomputer", label: "Mac")
#endif
}
extension Event: AppEntity {
static let defaultQuery = EventQuery()
static var typeDisplayRepresentation: TypeDisplayRepresentation {
TypeDisplayRepresentation("skdfj")
}
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation("eventsss")
}
}
struct EventQuery: EntityQuery, DynamicOptionsProvider {
typealias Entity = Event
@Dependency var vm: EventViewModel
func results() async throws -> some ResultsCollection {
return vm.events
}
// func defaultResult() async -> DefaultValue? {
// return vm.events[0]
// }
func entities(for identifiers: [Entity.ID]) async throws -> [Entity] {
return vm.events
}
func suggestedEntities() async throws -> some ResultsCollection {
return vm.events //lol cba
}
}
struct CompleteEvent: AppIntent {
static var title: LocalizedStringResource = "Complete An Event"
static var description = IntentDescription("Mark an Event as complete.")
@Parameter(title: "Event ID")
var eventID: String
func perform() async throws -> some IntentResult {
print("s")
let viewModel = EventViewModel()
print("hip")
guard let eventUUID = UUID(uuidString: eventID) else {
print(":sdklfajk")
return .result()
}
print("hii")
if let eventToModify = viewModel.events.firstIndex(where: { $0.id == eventUUID }) {
print("hiii")
viewModel.events[eventToModify].complete = true
viewModel.saveEvents()
}
return .result()
}
}

View File

@@ -18,6 +18,7 @@ struct NFSettings: Codable, Equatable {
var prevAppVersion: String = getVersion()+getBuildID()
}
@MainActor
class SettingsViewModel: ObservableObject {
@Published var settings: NFSettings = NFSettings()
@@ -36,7 +37,7 @@ class SettingsViewModel: ObservableObject {
"pink"
]
@Published var device: (sf: String, label: String)
@Published var device: (sf: String, label: String) = ("", "")
init(load: Bool = true) {
self.device = getDevice()

View File

@@ -8,7 +8,7 @@
import Foundation
class SymbolsLoader: ObservableObject {
@Published var allSymbols: [String] = []
private var allSymbols: [String] = []
init() {
self.allSymbols = getAllSymbols()
@@ -16,7 +16,7 @@ class SymbolsLoader: ObservableObject {
func getSymbols(_ searched: String) -> [String] {
if searched.isEmpty {
return allSymbols
return []
} else {
return allSymbols.filter() { $0.localizedCaseInsensitiveContains(searched) }
}

View File

@@ -1,76 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24053.1"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="collection view cell content view" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="s0d-6b-0kx">
<objects>
<viewController storyboardIdentifier="ViewController" id="Y6W-OH-hqX" customClass="ViewController" customModule="NearFuture" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="5EZ-qb-Rvc">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="xBf-3w-4Ao">
<rect key="frame" x="0.0" y="63" width="393" height="666"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<collectionViewFlowLayout key="collectionViewLayout" automaticEstimatedItemSize="YES" minimumLineSpacing="10" minimumInteritemSpacing="10" id="d2N-B5-D5N">
<size key="itemSize" width="100" height="100"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="symbolCell" id="nKx-2O-Nq5" customClass="SymbolCell" customModule="NearFuture" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<collectionViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="3DG-aV-ZH4">
<rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AaY-Po-OoS">
<rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gGM-kQ-cA7">
<rect key="frame" x="29" y="79" width="42" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</collectionViewCellContentView>
<connections>
<outlet property="imageView" destination="AaY-Po-OoS" id="Swa-vO-Mtz"/>
<outlet property="textLabel" destination="gGM-kQ-cA7" id="Qei-Qd-rn5"/>
</connections>
</collectionViewCell>
</cells>
</collectionView>
</subviews>
<viewLayoutGuide key="safeArea" id="vDu-zF-Fre"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
<connections>
<outlet property="collectionView" destination="xBf-3w-4Ao" id="c9h-Ew-aOX"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Ief-a0-LHa" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="139.69465648854961" y="65.492957746478879"/>
</scene>
</scenes>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View File

@@ -14,6 +14,8 @@ struct SymbolsPicker: View {
@FocusState var searchfocuesd: Bool
@State var searchInput: String = ""
@State var browsing: Bool = false
@Environment(\.dismiss) var dismiss
var symbols: [String] {
return symbolsLoader.getSymbols(searchInput)
@@ -29,7 +31,17 @@ struct SymbolsPicker: View {
NavigationStack {
GeometryReader { geo in
ScrollView {
if symbols.isEmpty {
if searchInput.isEmpty {
HStack {
Image(systemName: "magnifyingglass")
.resizable().scaledToFit()
.frame(width: 30)
Text("Start a Search")
.font(.title)
.bold()
}
.padding()
} else if symbols.isEmpty {
HStack {
Image(systemName: "magnifyingglass")
.resizable().scaledToFit()
@@ -45,6 +57,8 @@ struct SymbolsPicker: View {
ForEach(symbols, id: \.self) { symbol in
Button() {
selection = symbol
searchInput = ""
dismiss()
} label: {
VStack {
Image(systemName: symbol)
@@ -62,7 +76,19 @@ struct SymbolsPicker: View {
}
}
}
.searchable(text: $searchInput)
}
.searchable(text: $searchInput)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
if !browsing {
Button() {
searchInput = ""
dismiss()
} label: {
Label("Cancel", systemImage: "xmark")
}
}
}
}
}
}

View File

@@ -1,92 +0,0 @@
//
// SymbolsPickerStoryboard.swift
// NearFuture
//
// Created by neon443 on 14/06/2025.
//
import Foundation
import SwiftUI
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
var symbolLoader: SymbolsLoader = SymbolsLoader()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.itemSize = CGSize(
width: 100,
height: 100
)
}
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
}
}
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
symbolLoader.allSymbols.count
}
func collectionView(
_ collectionView: UICollectionView,
numberOfItemsInSection section: Int
) -> Int {
section
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "symbolCell", for: indexPath) as! SymbolCell
let imageView = cell.imageView
imageView?.image = UIImage(systemName: symbolLoader.allSymbols[indexPath.item])!
cell.textLabel?.text = "hi\(indexPath.row)"
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath.item)
}
}
class SymbolCell: UICollectionViewCell {
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var textLabel: UILabel!
}
struct SymbolsPickerStoryboardUIViewRepresentable: UIViewRepresentable {
class Coordinator {
var viewController: ViewController?
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
func makeUIView(context: Context) -> some UIView {
let storyboard = UIStoryboard(name: "SymbolsPicker", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
context.coordinator.viewController = viewController
return viewController.view
}
func updateUIView(_ uiView: UIViewType, context: Context) {
print()
}
}

View File

@@ -8,48 +8,49 @@
import Foundation
import SwiftUI
struct hapticHeavy: ViewModifier {
var trigger: any Equatable
struct hapticHeavy<T: Equatable>: ViewModifier {
var trigger: T
init(trigger: any Equatable) {
init(trigger: T) {
self.trigger = trigger
}
func body(content: Content) -> some View {
if #available(iOS 17, *) {
content
.sensoryFeedback(.impact(weight: .heavy, intensity: 1), trigger: trigger)
} else {
content
}
content
.onChange(of: trigger) { _ in
#if canImport(UIKit)
UIImpactFeedbackGenerator(style: .rigid).impactOccurred()
#endif
}
}
}
struct hapticSuccess<T: Equatable>: ViewModifier {
var trigger: T
init(trigger: T) {
self.trigger = trigger
}
func body(content: Content) -> some View {
content
.onChange(of: trigger) { _ in
#if canImport(UIKit)
UINotificationFeedbackGenerator().notificationOccurred(.success)
#endif
}
}
}
struct glassButton: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 19, macOS 16, *) {
content.buttonStyle(.glass)
} else {
content.buttonStyle(.borderedProminent)
.clipShape(RoundedRectangle(cornerRadius: 15))
.tint(.two)
}
}
}
struct hapticSuccess: ViewModifier {
var trigger: any Equatable
init(trigger: any Equatable) {
self.trigger = trigger
}
func body(content: Content) -> some View {
if #available(iOS 17, *) {
content.sensoryFeedback(.success, trigger: trigger)
} else {
content
}
#if swift(>=6.2)
content.buttonStyle(.glass)
#else
content.buttonStyle(.borderedProminent)
.clipShape(RoundedRectangle(cornerRadius: 15))
.tint(.two)
#endif
}
}