24 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
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
18 changed files with 214 additions and 239 deletions

View File

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

View File

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

View File

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

View File

@@ -639,6 +639,7 @@
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@@ -651,7 +652,7 @@
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 6.0;
}; };
name = Debug; name = Debug;
}; };
@@ -670,6 +671,7 @@
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@@ -682,7 +684,7 @@
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 6.0;
}; };
name = Release; name = Release;
}; };
@@ -835,6 +837,7 @@
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Near Future"; INFOPLIST_KEY_CFBundleDisplayName = "Near Future";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@@ -863,7 +866,8 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Debug; name = Debug;
@@ -887,6 +891,7 @@
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Near Future"; INFOPLIST_KEY_CFBundleDisplayName = "Near Future";
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@@ -912,7 +917,8 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Release; name = Release;

View File

@@ -35,9 +35,17 @@ struct ArchiveView: View {
) )
} label: { } label: {
EventListView(viewModel: viewModel, event: event) EventListView(viewModel: viewModel, event: event)
.id(event.complete) .id(event)
} }
.transition(.moveAndFadeReversed) .transition(.moveAndFadeReversed)
.contextMenu() {
Button(role: .destructive) {
viewModel.removeEvent(event)
} label: {
Label("Delete", systemImage: "trash")
.tint(.red)
}
}
} }
.padding(.horizontal) .padding(.horizontal)
} }

View File

@@ -16,8 +16,6 @@ struct AddEventView: View {
@State var showNeedsNameAlert: Bool = false @State var showNeedsNameAlert: Bool = false
@State var isSymbolPickerPresented: Bool = false @State var isSymbolPickerPresented: Bool = false
@State private var bye: Bool = false
@FocusState private var focusedField: Field? @FocusState private var focusedField: Field?
private enum Field { private enum Field {
case Name, Notes case Name, Notes
@@ -72,15 +70,15 @@ struct AddEventView: View {
// dscription // dscription
ZStack { ZStack {
TextField("Event Notes", text: $event.notes) if event.notes.isEmpty {
.textFieldStyle(RoundedBorderTextFieldStyle()) HStack {
.padding(.trailing, event.notes.isEmpty ? 0 : 30) Text("Event Notes")
.animation(.spring, value: event.notes) .opacity(0.5)
.focused($focusedField, equals: Field.Notes) Spacer()
.submitLabel(.done)
.onSubmit {
focusedField = nil
} }
}
TextEditor(text: $event.notes)
.lineLimit(10)
} }
ColorPicker("Event Color", selection: $event.color.colorBind) ColorPicker("Event Color", selection: $event.color.colorBind)
@@ -152,13 +150,14 @@ struct AddEventView: View {
viewModel.addEvent( viewModel.addEvent(
newEvent: event newEvent: event
) )
bye.toggle()
resetAddEventView() resetAddEventView()
#if canImport(UIKit)
UINotificationFeedbackGenerator().notificationOccurred(.success)
#endif
} label: { } label: {
Label("Save", systemImage: "checkmark") Label("Save", systemImage: "checkmark")
} }
.tint(.accent) .tint(.accent)
.modifier(hapticSuccess(trigger: bye))
.disabled(event.name.isEmpty) .disabled(event.name.isEmpty)
.onTapGesture { .onTapGesture {
if event.name.isEmpty { if event.name.isEmpty {
@@ -180,6 +179,9 @@ struct AddEventView: View {
Button() { Button() {
viewModel.editEvent(event) viewModel.editEvent(event)
dismiss() dismiss()
#if canImport(UIKit)
UINotificationFeedbackGenerator().notificationOccurred(.success)
#endif
} label: { } label: {
Label("Done", systemImage: "checkmark") Label("Done", systemImage: "checkmark")
} }

View File

@@ -90,19 +90,6 @@ struct EventListView: View {
.onTapGesture { .onTapGesture {
openWindow(value: event.id) 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")
}
}
} }
#else #else
var body: some View { var body: some View {
@@ -176,15 +163,9 @@ struct EventListView: View {
) )
.clipShape(RoundedRectangle(cornerRadius: 15)) .clipShape(RoundedRectangle(cornerRadius: 15))
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.contextMenu() { .swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) { Button(role: .destructive) {
let eventToModify = viewModel.events.firstIndex() { currEvent in viewModel.removeEvent(event)
currEvent.id == event.id
}
if let eventToModify = eventToModify {
viewModel.events.remove(at: eventToModify)
viewModel.saveEvents()
}
} label: { } label: {
Label("Delete", systemImage: "trash") Label("Delete", systemImage: "trash")
} }
@@ -196,16 +177,6 @@ struct EventListView: View {
#Preview("EventListView") { #Preview("EventListView") {
let vm = dummyEventViewModel() let vm = dummyEventViewModel()
ZStack { ZStack {
Color.black
VStack {
ForEach(0..<50) { _ in
Rectangle()
.foregroundStyle(randomColor().opacity(0.5))
.padding(-10)
}
.ignoresSafeArea(.all)
.blur(radius: 5)
}
VStack { VStack {
ForEach(vm.events) { event in ForEach(vm.events) { event in
EventListView( EventListView(

View File

@@ -59,9 +59,17 @@ struct HomeView: View {
) )
} label: { } label: {
EventListView(viewModel: viewModel, event: event) EventListView(viewModel: viewModel, event: event)
.id(event.complete) .id(event)
} }
.transition(.moveAndFade) .transition(.moveAndFade)
.contextMenu() {
Button(role: .destructive) {
viewModel.removeEvent(event)
} label: {
Label("Delete", systemImage: "trash")
.tint(.red)
}
}
} }
.padding(.horizontal) .padding(.horizontal)
// } // }

View File

@@ -66,65 +66,19 @@ struct ImportView: View {
fgColor = .yellow fgColor = .yellow
} }
} }
.blur(radius: showAlert ? 2 : 0) .alert("Are you sure?", isPresented: $showAlert) {
Group { Button(role: .destructive) {
Rectangle() importEvents()
.frame(maxWidth: .infinity, maxHeight: .infinity) } label: {
.foregroundStyle(replaceCurrentEvents ? .red.opacity(0.25) : .black.opacity(0.2)) Text("Replace Events")
.animation(.default, value: replaceCurrentEvents) }
.ignoresSafeArea() Button() {
ZStack { importEvents()
Rectangle() } label: {
.clipShape(RoundedRectangle(cornerRadius: 25)) Text("Add to Events")
VStack(alignment: .center) { .foregroundStyle(.one)
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()
} }
.frame(maxWidth: 250, maxHeight: 250)
} }
.opacity(showAlert ? 1 : 0)
} }
} }
} }

View File

@@ -154,25 +154,25 @@ struct EventWidgetView: View {
.foregroundColor(event.date < Date() ? .red : .primary) .foregroundColor(event.date < Date() ? .red : .primary)
.padding(.trailing, -12) .padding(.trailing, -12)
} else { } else {
Button( // Button(
intent: CompleteEvent( // intent: CompleteEvent(
eventID: IntentParameter( // eventID: IntentParameter(
title: LocalizedStringResource( // title: LocalizedStringResource(
stringLiteral: event.id.uuidString // stringLiteral: event.id.uuidString
) // )
) // )
) // )
) { // ) {
if event.complete { // if event.complete {
Circle() // Circle()
.frame(width: 10) // .frame(width: 10)
.foregroundStyle(.green) // .foregroundStyle(.green)
} else { // } else {
Circle() // Circle()
.frame(width: 10) // .frame(width: 10)
.foregroundStyle(.gray) // .foregroundStyle(.gray)
} // }
} // }
Text(daysUntilEvent(event.date).long) Text(daysUntilEvent(event.date).long)
.font(.caption) .font(.caption)
.multilineTextAlignment(.trailing) .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. **Near Future** is a SwiftUI App to help people track upcoming events - Holidays, Trips, Birthdays, Weddings, Anniversaries.

View File

@@ -11,7 +11,7 @@ struct CompleteEventButton: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@Binding var event: Event @Binding var event: Event
@State var timer: Timer? @MainActor @State var timer: Timer?
@State var largeTick: Bool = false @State var largeTick: Bool = false
@State var completeInProgress: Bool = false @State var completeInProgress: Bool = false
@State var completeStartTime: Date = .now @State var completeStartTime: Date = .now
@@ -34,20 +34,28 @@ struct CompleteEventButton: View {
completeStartTime = .now completeStartTime = .now
progress = 0 progress = 0
timer = Timer(timeInterval: 0.01, repeats: true) { timer in timer = Timer(timeInterval: 0.02, repeats: true) { timer in
guard completeInProgress else { return } DispatchQueue.main.async {
guard timer.isValid else { return } guard completeInProgress else { return }
let elapsed = Date().timeIntervalSince(completeStartTime) guard let timer = self.timer else { return }
progress = min(1, elapsed) guard timer.isValid else { return }
let elapsed = Date().timeIntervalSince(completeStartTime)
if progress >= 1 { progress = min(1, elapsed)
withAnimation { completeInProgress = false }
viewModel.completeEvent(&event)
#if canImport(UIKit) #if canImport(UIKit)
UINotificationFeedbackGenerator().notificationOccurred(.success) UIImpactFeedbackGenerator(style: .light).impactOccurred()
#endif #endif
timer.invalidate()
progress = 0 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) RunLoop.main.add(timer!, forMode: .common)

View File

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

View File

@@ -160,20 +160,24 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
eventUUIDs.remove(at: remove) eventUUIDs.remove(at: remove)
} }
let components = getDateComponents(events[index].date) let components = getDateComponents(events[index].date)
//check the notif matches event details //check the notif matches event details
if req.content.title == events[index].name, if req.content.title == events[index].name,
req.content.subtitle == events[index].notes, req.content.subtitle == events[index].notes,
req.trigger == UNCalendarNotificationTrigger(dateMatching: components, repeats: false) { req.trigger == UNCalendarNotificationTrigger(dateMatching: components, repeats: false) {
//if it does, make sure the notif delets if u complete the veent //if it does, make sure the notif delets if u complete the veent or in the past
if events[index].complete { if events[index].complete || events[index].date > .now {
cancelNotif(req.identifier) cancelNotif(req.identifier)
} else {
//dont cancel the notif
} }
} else { } else {
//reschedult it because the event details have changed
cancelNotif(req.identifier) cancelNotif(req.identifier)
scheduleEventNotif(events[index]) scheduleEventNotif(events[index])
} }
} else { } else {
//cancel if the event is deleted //cancel notif if the event is deleted (doesnt exist/cannot be matched)
cancelNotif(req.identifier) cancelNotif(req.identifier)
} }
} }
@@ -182,6 +186,14 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
scheduleEventNotif(event) 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 // save to local and icloud
@@ -240,8 +252,13 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
} }
} }
func removeEvent(at index: IndexSet) { func removeEvent(_ eventToRemove: Event) {
events.remove(atOffsets: index) 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 saveEvents() //sync local and icl
} }
@@ -490,6 +507,7 @@ func getBuildID() -> String {
return "\(build)" return "\(build)"
} }
@MainActor
func getDevice() -> (sf: String, label: String) { func getDevice() -> (sf: String, label: String) {
#if canImport(UIKit) #if canImport(UIKit)
let asi = ProcessInfo().isiOSAppOnMac let asi = ProcessInfo().isiOSAppOnMac
@@ -507,57 +525,3 @@ func getDevice() -> (sf: String, label: String) {
return (sf: "desktopcomputer", label: "Mac") return (sf: "desktopcomputer", label: "Mac")
#endif #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() var prevAppVersion: String = getVersion()+getBuildID()
} }
@MainActor
class SettingsViewModel: ObservableObject { class SettingsViewModel: ObservableObject {
@Published var settings: NFSettings = NFSettings() @Published var settings: NFSettings = NFSettings()
@@ -36,7 +37,7 @@ class SettingsViewModel: ObservableObject {
"pink" "pink"
] ]
@Published var device: (sf: String, label: String) @Published var device: (sf: String, label: String) = ("", "")
init(load: Bool = true) { init(load: Bool = true) {
self.device = getDevice() self.device = getDevice()

View File

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

View File

@@ -31,7 +31,17 @@ struct SymbolsPicker: View {
NavigationStack { NavigationStack {
GeometryReader { geo in GeometryReader { geo in
ScrollView { 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 { HStack {
Image(systemName: "magnifyingglass") Image(systemName: "magnifyingglass")
.resizable().scaledToFit() .resizable().scaledToFit()

View File

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