47 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
neon443
a40d4f4300 ui cleanup 2025-06-14 18:53:34 +01:00
neon443
22af9fc060 hell nah fuck this 2025-06-14 18:27:13 +01:00
neon443
2dd2c51059 uikit shenanigans: collectionview for the symbol picker?? 2025-06-14 17:45:38 +01:00
neon443
4c9e72fad2 viewmodifier for backwards compat 2025-06-14 11:20:29 +01:00
neon443
cb26c69492 added a little help fow when nothing matches search 2025-06-14 11:17:41 +01:00
neon443
13ef94ea3e custom symbols picker 2025-06-14 10:59:48 +01:00
neon443
6e5b7adbc4 fix editing events on mac - they now reload on he fly
add search on mac
fix symbolspicker on mac
2025-06-14 10:24:03 +01:00
neon443
659f14d5a3 add events on mac 2025-06-13 22:00:28 +01:00
neon443
0663ba9e59 add settings on mac
add whats new on mac
clean up settings on ios
clean up whats new + liquid glass
2025-06-13 21:28:37 +01:00
neon443
6e7bc6c2d1 add viewmodifiers.siwft to conditionally do viewmodifiers
replaced all the .apply s everywhere
colorcodable into its own file
add icon changing for mac
2025-06-13 18:30:07 +01:00
neon443
3f21074091 moved icloud ui functions into viewModel
- idk why it was in the view
added settings for mac
fix addeventview
export view pasteboard for mac
move accent icon to new file
Archive view reversed - new to old
2025-06-13 17:15:23 +01:00
neon443
ee2e05c523 fix the cooked addeventview 2025-06-11 18:49:09 +01:00
neon443
e9e19e2659 Large rework:
Liquid Glass:
- remove ultrathinmaterial from all sheets
- button color change

remove the eventName eventDate etcs and replaced with an event var

Add addeventview for mac, cleanup #if s

clean up homeview
add .searchable
addeventview is horizontal i have no idea why
2025-06-11 18:34:17 +01:00
neon443
5ce48a4bc2 add new buttons 2025-06-10 15:09:10 +01:00
neon443
a7e09692f6 liquid glass update 1 2025-06-10 13:14:19 +01:00
neon443
559f891719 new liquid glass icon 2025-06-10 12:28:03 +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
39 changed files with 1376 additions and 1082 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 = 4.4.0 VERSION = 5.0.1
NAME = Near Future NAME = Near Future
BUILD_NUMBER = 1 BUILD_NUMBER = 52

View File

@@ -24,31 +24,35 @@ struct NearFutureApp: App {
.defaultSize(width: 550, height: 650) .defaultSize(width: 550, height: 650)
.commands { .commands {
CommandGroup(replacing: CommandGroupPlacement.appInfo) { CommandGroup(replacing: CommandGroupPlacement.appInfo) {
Button("about nf") { Button("About Near Future") {
openWindow(id: "about") openWindow(id: "about")
} }
} }
NearFutureCommands() NearFutureCommands()
} }
WindowGroup("edit Event", for: Event.ID.self) { $eventID in WindowGroup("Edit Event", for: Event.ID.self) { $eventID in
if viewModel.events.first(where: {$0.id == eventID}) == nil {
AddEventView(
viewModel: viewModel
)
} else {
EditEventView( EditEventView(
viewModel: viewModel, viewModel: viewModel,
event: Binding( event: Binding(
get: { get: {
viewModel.events.first(where: {$0.id == eventID})! viewModel.events.first(where: {$0.id == eventID}) ?? viewModel.template
}, },
set: { newValue in set: { newValue in
if let eventIndex = viewModel.events.firstIndex(where: { viewModel.editEvent(newValue)
$0.id == eventID
}) {
viewModel.events[eventIndex] = newValue
}
viewModel.saveEvents()
} }
) )
) )
} }
}
.defaultSize(width: 480, height: 550)
.windowIdealSize(.fitToContent)
.restorationBehavior(.disabled)
Window("About Near Future", id: "about") { Window("About Near Future", id: "about") {
AboutView() AboutView()
@@ -59,7 +63,10 @@ struct NearFutureApp: App {
.defaultPosition(UnitPoint.center) .defaultPosition(UnitPoint.center)
Settings { Settings {
Text("wip") SettingsView(
viewModel: viewModel,
settingsModel: settingsModel
)
} }
} }
} }

View File

@@ -11,7 +11,7 @@ import SwiftUI
struct NearFutureCommands: Commands { struct NearFutureCommands: Commands {
var body: some Commands { var body: some Commands {
CommandGroup(after: CommandGroupPlacement.appInfo) { CommandGroup(after: CommandGroupPlacement.appInfo) {
Text("hi") // Text("hi")
} }
} }
} }

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

@@ -11,8 +11,11 @@ struct ContentView: View {
@StateObject var viewModel: EventViewModel @StateObject var viewModel: EventViewModel
@StateObject var settingsModel: SettingsViewModel @StateObject var settingsModel: SettingsViewModel
@State private var showAddEventView: Bool = false
@State private var symbolSearchInput: String = ""
var body: some View { var body: some View {
NavigationSplitView(preferredCompactColumn: .constant(.sidebar)) { NavigationSplitView {
List { List {
NavigationLink { NavigationLink {
HomeView( HomeView(
@@ -32,13 +35,48 @@ struct ContentView: View {
Image(systemName: "tray.full") Image(systemName: "tray.full")
Text("Archive") Text("Archive")
} }
NavigationLink {
SymbolsPicker(
selection: .constant(""),
browsing: true
)
} label: {
Image(systemName: "star.circle")
Text("Symbols")
}
NavigationLink {
SettingsView(
viewModel: viewModel,
settingsModel: settingsModel
)
} label: {
Image(systemName: "gear")
Text("Settings")
}
} }
} detail: { } detail: {
Text("Welcome to Near Future")
} }
.tint(settingsModel.settings.tint.color) .tint(settingsModel.settings.tint.color)
.frame(minWidth: 450, minHeight: 550) .frame(minWidth: 450, minHeight: 550)
.containerBackground(.ultraThinMaterial, for: .window) .containerBackground(.regularMaterial, for: .window)
.sheet(isPresented: $settingsModel.settings.showWhatsNew) {
WhatsNewView(settingsModel: settingsModel)
.presentationSizing(.form)
}
.sheet(isPresented: $showAddEventView) {
AddEventView(
viewModel: viewModel
)
.presentationSizing(.page)
}
.toolbar {
Button() {
showAddEventView.toggle()
} label: {
Label("New", systemImage: "plus")
}
}
} }
} }

View File

@@ -1,185 +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
)
.apply {
if #available(iOS 17, *) {
$0.sensoryFeedback(.success, trigger: event.complete)
}
}
}
.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

@@ -11,20 +11,41 @@ struct HomeView: View {
@StateObject var viewModel: EventViewModel @StateObject var viewModel: EventViewModel
@StateObject var settingsModel: SettingsViewModel @StateObject var settingsModel: SettingsViewModel
@State private var searchInput: String = ""
var filteredEvents: [Event] { var filteredEvents: [Event] {
switch settingsModel.settings.showCompletedInHome { if searchInput.isEmpty {
case true: if settingsModel.settings.showCompletedInHome {
return viewModel.events return viewModel.events
case false: } else {
return viewModel.events.filter { !$0.complete } return viewModel.events.filter() { !$0.complete }
}
} else {
return viewModel.events.filter {
$0.name.localizedCaseInsensitiveContains(searchInput) ||
$0.notes.localizedCaseInsensitiveContains(searchInput)
} }
} }
}
var body: some View { var body: some View {
ScrollView { ScrollView {
ForEach(filteredEvents) { event in ForEach(viewModel.events) { event in
if filteredEvents.contains(event) {
EventListView(viewModel: viewModel, event: event) EventListView(viewModel: viewModel, event: event)
.id(event)
.contextMenu() {
Button(role: .destructive) {
viewModel.removeEvent(event)
} label: {
Label("Delete", systemImage: "trash")
.tint(.red)
} }
} }
}
}
}
.searchable(text: $searchInput)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
} }
} }

View File

@@ -1,48 +0,0 @@
//
// EditEventView.swift
// NearFuture
//
// Created by neon443 on 21/05/2025.
//
import SwiftUI
struct EditEventView: View {
@Environment(\.dismiss) var dismiss
@ObservedObject var viewModel: EventViewModel
@Binding var event: Event
var body: some View {
AddEventView(
viewModel: viewModel,
eventName: $event.name,
eventComplete: $event.complete,
eventCompleteDesc: $event.completeDesc,
eventSymbol: $event.symbol,
eventColor: $event.color.colorBind,
eventNotes: $event.notes,
eventDate: $event.date,
eventRecurrence: $event.recurrence,
adding: false //bc we editing existing event
)
.navigationTitle("Edit Event")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button() {
dismiss()
} label: {
Text("Done")
.bold()
}
.disabled(event.name == "")
}
}
}
}
#Preview {
EditEventView(
viewModel: dummyEventViewModel(),
event: .constant(dummyEventViewModel().template)
)
}

View File

@@ -0,0 +1,155 @@
//
// SettingsView.swift
// NearFuture
//
// Created by neon443 on 13/06/2025.
//
import SwiftUI
struct SettingsView: View {
@ObservedObject var viewModel: EventViewModel
@ObservedObject var settingsModel: SettingsViewModel
@State private var importStr: String = ""
func changeIcon(to toIcon: String) {
if let nsimage = NSImage(named: toIcon) {
let nsImageView = NSImageView(image: nsimage)
nsImageView.frame = NSRect(x: 0, y: 0, width: 128, height: 128)
NSApplication.shared.dockTile.contentView = nsImageView
NSApplication.shared.dockTile.display()
}
}
var body: some View {
NavigationStack {
List {
ScrollView(.horizontal) {
HStack {
ForEach(settingsModel.accentChoices, id: \.self) { choice in
let color = Color(nsColor: NSColor(named: "uiColors/\(choice)")!)
ZStack {
Button() {
settingsModel.changeTint(to: choice)
changeIcon(to: choice)
} label: {
Circle()
.foregroundStyle(color)
.frame(width: 30)
}
.buttonStyle(.plain)
if ColorCodable(color) == settingsModel.settings.tint {
let needContrast: Bool = ColorCodable(color) == settingsModel.settings.tint
Circle()
.foregroundStyle(needContrast ? .two : .one)
.frame(width: 10)
}
}
}
}
}
Button("Show What's New") {
settingsModel.settings.showWhatsNew = true
}
Toggle("Show completed Events in Home", isOn: $settingsModel.settings.showCompletedInHome)
.onChange(of: settingsModel.settings.showCompletedInHome) { _ in
settingsModel.saveSettings()
}
NavigationLink() {
List {
if !settingsModel.notifsGranted {
Text("\(Image(systemName: "xmark")) Notifications disabled for Near Future")
.foregroundStyle(.red)
Button("Request Notifications") {
Task.detached {
let requestNotifsResult = await requestNotifs()
await MainActor.run {
settingsModel.notifsGranted = requestNotifsResult
}
}
}
} else {
Text("\(Image(systemName: "checkmark")) Notifications enabled for Near Future")
.foregroundStyle(.green)
}
}
} label: {
Image(systemName: "bell.badge.fill")
Text("Notifications")
}
NavigationLink() {
iCloudSettingsView(
viewModel: viewModel,
settingsModel: settingsModel
)
} label: {
HStack {
Image(systemName: "icloud.fill")
Text("iCloud")
Spacer()
Circle()
.frame(width: 20, height: 20)
.foregroundStyle(viewModel.iCloudStatusColor)
}
}
.onAppear {
viewModel.sync()
viewModel.updateiCStatus()
}
NavigationLink() {
ImportView(viewModel: viewModel, importStr: $importStr)
} label: {
Label("Import Events", systemImage: "tray.and.arrow.down.fill")
.foregroundStyle(.one)
}
NavigationLink() {
ExportView(viewModel: viewModel)
} label: {
Label("Export Events", systemImage: "square.and.arrow.up")
.foregroundStyle(.one)
}
Text("Tip")
.font(.subheadline)
Text("Near Future has Widgets!")
Text("Danger Zone")
.foregroundStyle(.red)
.font(.subheadline)
Button("Delete local data", role: .destructive) {
viewModel.dangerClearLocalData()
}
Button("Delete iCloud data", role: .destructive) {
viewModel.dangerCleariCloudData()
}
Button("Delete all data", role: .destructive) {
viewModel.dangerClearLocalData()
viewModel.dangerCleariCloudData()
}
Text("Debug")
.foregroundStyle(.red)
.font(.subheadline)
Button("Reset UserDefaults", role: .destructive) {
viewModel.dangerResetLocalData()
}
Button("Reset iCloud", role: .destructive) {
viewModel.dangerResetiCloud()
}
// AboutView()
.modifier(navigationInlineLarge())
.scrollContentBackground(.hidden)
}
}
}
}
#Preview {
SettingsView(
viewModel: dummyEventViewModel(),
settingsModel: dummySettingsViewModel()
)
}

View File

@@ -14,10 +14,8 @@
A90D49442DDE1C7600781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; }; A90D49442DDE1C7600781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; };
A90D49452DDE1C7600781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; }; A90D49452DDE1C7600781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; };
A90D49462DDE1C7A00781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; }; A90D49462DDE1C7A00781124 /* Tints.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A90D49432DDE1C1100781124 /* Tints.xcassets */; };
A90D494B2DDE2C2900781124 /* AddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83C2DCAABE00064DCA0 /* AddEventView.swift */; };
A90D49522DDE2D0000781124 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49512DDE2D0000781124 /* Extensions.swift */; }; A90D49522DDE2D0000781124 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49512DDE2D0000781124 /* Extensions.swift */; };
A90D49532DDE2D0000781124 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49512DDE2D0000781124 /* Extensions.swift */; }; A90D49532DDE2D0000781124 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49512DDE2D0000781124 /* Extensions.swift */; };
A90D49562DDE2D5800781124 /* SFSymbolsPicker in Frameworks */ = {isa = PBXBuildFile; productRef = A90D49552DDE2D5800781124 /* SFSymbolsPicker */; };
A90D495B2DDE2EDB00781124 /* MacNearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495A2DDE2EDB00781124 /* MacNearFutureApp.swift */; }; A90D495B2DDE2EDB00781124 /* MacNearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495A2DDE2EDB00781124 /* MacNearFutureApp.swift */; };
A90D495E2DDE3C7400781124 /* NFCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495D2DDE3C7400781124 /* NFCommands.swift */; }; A90D495E2DDE3C7400781124 /* NFCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495D2DDE3C7400781124 /* NFCommands.swift */; };
A90D495F2DDE3C7400781124 /* NFCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495D2DDE3C7400781124 /* NFCommands.swift */; }; A90D495F2DDE3C7400781124 /* NFCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495D2DDE3C7400781124 /* NFCommands.swift */; };
@@ -27,11 +25,26 @@
A914FA4B2DD26C6800856265 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4A2DD26C0F00856265 /* HomeView.swift */; }; A914FA4B2DD26C6800856265 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4A2DD26C0F00856265 /* HomeView.swift */; };
A914FA4D2DD2768900856265 /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4C2DD2768900856265 /* WhatsNewView.swift */; }; A914FA4D2DD2768900856265 /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4C2DD2768900856265 /* WhatsNewView.swift */; };
A914FA4F2DD276D200856265 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4E2DD276D200856265 /* AboutView.swift */; }; A914FA4F2DD276D200856265 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4E2DD276D200856265 /* AboutView.swift */; };
A91EF8072DFC8B8B00B8463D /* ColorCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF8062DFC8B8B00B8463D /* ColorCodable.swift */; };
A91EF8082DFC8B8B00B8463D /* ColorCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF8062DFC8B8B00B8463D /* ColorCodable.swift */; };
A91EF8092DFC8B8B00B8463D /* ColorCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF8062DFC8B8B00B8463D /* ColorCodable.swift */; };
A91EF80B2DFC910000B8463D /* ViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF80A2DFC910000B8463D /* ViewModifiers.swift */; };
A91EF80C2DFC910000B8463D /* ViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF80A2DFC910000B8463D /* ViewModifiers.swift */; };
A91EF80D2DFC910000B8463D /* ViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF80A2DFC910000B8463D /* ViewModifiers.swift */; };
A91EF80E2DFC9A0C00B8463D /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4C2DD2768900856265 /* WhatsNewView.swift */; };
A91EF8102DFCB66C00B8463D /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF80F2DFCB66C00B8463D /* SettingsView.swift */; };
A91EF8132DFCC87D00B8463D /* EditEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83D2DCAABE00064DCA0 /* EditEventView.swift */; };
A91EF8142DFCC87D00B8463D /* AddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83C2DCAABE00064DCA0 /* AddEventView.swift */; };
A91EF8182DFD77BF00B8463D /* SymbolsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */; };
A91EF8192DFD77BF00B8463D /* SymbolsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */; };
A91EF81A2DFD77BF00B8463D /* SymbolsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */; };
A91EF81C2DFD796600B8463D /* SymbolsPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */; };
A91EF81D2DFD796600B8463D /* SymbolsPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */; };
A91EF81E2DFD796600B8463D /* SymbolsPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */; };
A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2872D24011400E4F9B1 /* NearFutureApp.swift */; }; A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C2872D24011400E4F9B1 /* NearFutureApp.swift */; };
A920C28C2D24011400E4F9B1 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Events.swift */; }; A920C28C2D24011400E4F9B1 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Events.swift */; };
A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C28D2D24011A00E4F9B1 /* Assets.xcassets */; }; A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C28D2D24011A00E4F9B1 /* Assets.xcassets */; };
A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C2912D24011A00E4F9B1 /* Preview Assets.xcassets */; }; A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C2912D24011A00E4F9B1 /* Preview Assets.xcassets */; };
A920C2BE2D24021A00E4F9B1 /* SFSymbolsPicker in Frameworks */ = {isa = PBXBuildFile; productRef = A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */; };
A949F8322DCAAA8A0064DCA0 /* NearFutureIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = A949F8312DCAAA8A0064DCA0 /* NearFutureIcon.png */; }; A949F8322DCAAA8A0064DCA0 /* NearFutureIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = A949F8312DCAAA8A0064DCA0 /* NearFutureIcon.png */; };
A949F84B2DCAABE00064DCA0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83A2DCAABE00064DCA0 /* ArchiveView.swift */; }; A949F84B2DCAABE00064DCA0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83A2DCAABE00064DCA0 /* ArchiveView.swift */; };
A949F84C2DCAABE00064DCA0 /* AddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83C2DCAABE00064DCA0 /* AddEventView.swift */; }; A949F84C2DCAABE00064DCA0 /* AddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83C2DCAABE00064DCA0 /* AddEventView.swift */; };
@@ -45,16 +58,24 @@
A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8462DCAABE00064DCA0 /* SettingsView.swift */; }; A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8462DCAABE00064DCA0 /* SettingsView.swift */; };
A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8482DCAABE00064DCA0 /* StatsView.swift */; }; A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8482DCAABE00064DCA0 /* StatsView.swift */; };
A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F85D2DCABB420064DCA0 /* Buttons.swift */; }; A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F85D2DCABB420064DCA0 /* Buttons.swift */; };
A95E9ED32DFC703200ED655F /* iCloudSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8442DCAABE00064DCA0 /* iCloudSettingsView.swift */; };
A95E9ED82DFC742B00ED655F /* AccentIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95E9ED72DFC742B00ED655F /* AccentIcon.swift */; };
A95E9ED92DFC742B00ED655F /* AccentIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95E9ED72DFC742B00ED655F /* AccentIcon.swift */; };
A95E9EDA2DFC742B00ED655F /* AccentIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95E9ED72DFC742B00ED655F /* AccentIcon.swift */; };
A95E9EE42DFC77D400ED655F /* ImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8452DCAABE00064DCA0 /* ImportView.swift */; };
A95E9EE52DFC77E200ED655F /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8432DCAABE00064DCA0 /* ExportView.swift */; };
A96609E72DFD800000DBFA78 /* HelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8412DCAABE00064DCA0 /* HelpView.swift */; };
A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */; }; A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */; };
A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */; }; A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */; };
A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A979F60F2D270AF80094C0B3 /* Assets.xcassets */; }; 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, ); }; }; 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 */; }; A979F6182D2714310094C0B3 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Events.swift */; };
A98C20CB2DE730740008D61C /* EventListViewMac.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CA2DE730740008D61C /* EventListViewMac.swift */; };
A98C20CC2DE730740008D61C /* EditEventViewMac.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20C92DE730740008D61C /* EditEventViewMac.swift */; };
A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CD2DE7308E0008D61C /* ArchiveView.swift */; }; A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CD2DE7308E0008D61C /* ArchiveView.swift */; };
A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CF2DE731BD0008D61C /* HomeView.swift */; }; A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CF2DE731BD0008D61C /* HomeView.swift */; };
A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20D32DE7339E0008D61C /* AboutView.swift */; }; A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20D32DE7339E0008D61C /* AboutView.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 */; }; A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -104,6 +125,11 @@
A914FA4A2DD26C0F00856265 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; }; A914FA4A2DD26C0F00856265 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
A914FA4C2DD2768900856265 /* WhatsNewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WhatsNewView.swift; path = NearFuture/Views/Settings/WhatsNewView.swift; sourceTree = SOURCE_ROOT; }; A914FA4C2DD2768900856265 /* WhatsNewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WhatsNewView.swift; path = NearFuture/Views/Settings/WhatsNewView.swift; sourceTree = SOURCE_ROOT; };
A914FA4E2DD276D200856265 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AboutView.swift; path = NearFuture/Views/Misc/AboutView.swift; sourceTree = SOURCE_ROOT; }; A914FA4E2DD276D200856265 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AboutView.swift; path = NearFuture/Views/Misc/AboutView.swift; sourceTree = SOURCE_ROOT; };
A91EF8062DFC8B8B00B8463D /* ColorCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorCodable.swift; sourceTree = "<group>"; };
A91EF80A2DFC910000B8463D /* ViewModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifiers.swift; sourceTree = "<group>"; };
A91EF80F2DFCB66C00B8463D /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolsLoader.swift; sourceTree = "<group>"; };
A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolsPicker.swift; sourceTree = "<group>"; };
A920C2842D24011400E4F9B1 /* NearFuture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NearFuture.app; sourceTree = BUILT_PRODUCTS_DIR; }; A920C2842D24011400E4F9B1 /* NearFuture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NearFuture.app; sourceTree = BUILT_PRODUCTS_DIR; };
A920C2872D24011400E4F9B1 /* NearFutureApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureApp.swift; sourceTree = "<group>"; }; A920C2872D24011400E4F9B1 /* NearFutureApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureApp.swift; sourceTree = "<group>"; };
A920C28B2D24011400E4F9B1 /* Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = "<group>"; }; A920C28B2D24011400E4F9B1 /* Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = "<group>"; };
@@ -125,15 +151,15 @@
A949F8462DCAABE00064DCA0 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; }; A949F8462DCAABE00064DCA0 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
A949F8482DCAABE00064DCA0 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; }; A949F8482DCAABE00064DCA0 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = "<group>"; };
A949F85D2DCABB420064DCA0 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; }; A949F85D2DCABB420064DCA0 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; };
A95E9ED72DFC742B00ED655F /* AccentIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccentIcon.swift; sourceTree = "<group>"; };
A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NearFutureWidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NearFutureWidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsBundle.swift; sourceTree = "<group>"; }; 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>"; }; 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>"; }; A979F60F2D270AF80094C0B3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
A98C20C92DE730740008D61C /* EditEventViewMac.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditEventViewMac.swift; 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>"; }; 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>"; }; 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>"; }; A98C20D32DE7339E0008D61C /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.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>"; }; A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@@ -142,7 +168,6 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A90D49562DDE2D5800781124 /* SFSymbolsPicker in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -150,7 +175,6 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A920C2BE2D24021A00E4F9B1 /* SFSymbolsPicker in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -169,6 +193,9 @@
children = ( children = (
A920C28B2D24011400E4F9B1 /* Events.swift */, A920C28B2D24011400E4F9B1 /* Events.swift */,
A90D49602DDE626300781124 /* Settings.swift */, A90D49602DDE626300781124 /* Settings.swift */,
A91EF8062DFC8B8B00B8463D /* ColorCodable.swift */,
A95E9ED72DFC742B00ED655F /* AccentIcon.swift */,
A91EF8162DFD77A500B8463D /* SymbolsPicker */,
); );
path = Model; path = Model;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -190,9 +217,8 @@
children = ( children = (
A90D49332DDE0FAF00781124 /* ContentViewMac.swift */, A90D49332DDE0FAF00781124 /* ContentViewMac.swift */,
A98C20CF2DE731BD0008D61C /* HomeView.swift */, A98C20CF2DE731BD0008D61C /* HomeView.swift */,
A98C20CA2DE730740008D61C /* EventListViewMac.swift */,
A98C20CD2DE7308E0008D61C /* ArchiveView.swift */, A98C20CD2DE7308E0008D61C /* ArchiveView.swift */,
A98C20C82DE730420008D61C /* Modification */, A91EF80F2DFCB66C00B8463D /* SettingsView.swift */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -203,6 +229,8 @@
A920C2872D24011400E4F9B1 /* NearFutureApp.swift */, A920C2872D24011400E4F9B1 /* NearFutureApp.swift */,
A90D49512DDE2D0000781124 /* Extensions.swift */, A90D49512DDE2D0000781124 /* Extensions.swift */,
A90D49202DDE0A3B00781124 /* Model */, A90D49202DDE0A3B00781124 /* Model */,
A91EF80A2DFC910000B8463D /* ViewModifiers.swift */,
A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */,
); );
path = Shared; path = Shared;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -214,6 +242,15 @@
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
A91EF8162DFD77A500B8463D /* SymbolsPicker */ = {
isa = PBXGroup;
children = (
A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */,
A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */,
);
path = SymbolsPicker;
sourceTree = "<group>";
};
A920C27B2D24011300E4F9B1 = { A920C27B2D24011300E4F9B1 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -351,14 +388,6 @@
path = NearFutureWidgets; path = NearFutureWidgets;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
A98C20C82DE730420008D61C /* Modification */ = {
isa = PBXGroup;
children = (
A98C20C92DE730740008D61C /* EditEventViewMac.swift */,
);
path = Modification;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@@ -374,11 +403,9 @@
); );
dependencies = ( dependencies = (
A98C20D22DE732B10008D61C /* PBXTargetDependency */, A98C20D22DE732B10008D61C /* PBXTargetDependency */,
A90D494D2DDE2C6000781124 /* PBXTargetDependency */,
); );
name = MacNearFuture; name = MacNearFuture;
packageProductDependencies = ( packageProductDependencies = (
A90D49552DDE2D5800781124 /* SFSymbolsPicker */,
); );
productName = MacNearFuture; productName = MacNearFuture;
productReference = A90D49262DDE0FA400781124 /* Near Future.app */; productReference = A90D49262DDE0FA400781124 /* Near Future.app */;
@@ -400,7 +427,6 @@
); );
name = NearFuture; name = NearFuture;
packageProductDependencies = ( packageProductDependencies = (
A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */,
); );
productName = NearFuture; productName = NearFuture;
productReference = A920C2842D24011400E4F9B1 /* NearFuture.app */; productReference = A920C2842D24011400E4F9B1 /* NearFuture.app */;
@@ -454,7 +480,6 @@
); );
mainGroup = A920C27B2D24011300E4F9B1; mainGroup = A920C27B2D24011300E4F9B1;
packageReferences = ( packageReferences = (
A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */,
); );
productRefGroup = A920C2852D24011400E4F9B1 /* Products */; productRefGroup = A920C2852D24011400E4F9B1 /* Products */;
projectDirPath = ""; projectDirPath = "";
@@ -505,18 +530,30 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A98C20CB2DE730740008D61C /* EventListViewMac.swift in Sources */, A91EF80E2DFC9A0C00B8463D /* WhatsNewView.swift in Sources */,
A98C20CC2DE730740008D61C /* EditEventViewMac.swift in Sources */, A91EF8192DFD77BF00B8463D /* SymbolsLoader.swift in Sources */,
A90D494B2DDE2C2900781124 /* AddEventView.swift in Sources */, A95E9EE42DFC77D400ED655F /* ImportView.swift in Sources */,
A9BAC6892DFF242300EC8E44 /* CompleteEventButton.swift in Sources */,
A91EF80C2DFC910000B8463D /* ViewModifiers.swift in Sources */,
A95E9ED92DFC742B00ED655F /* AccentIcon.swift in Sources */,
A91EF8102DFCB66C00B8463D /* SettingsView.swift in Sources */,
A90D495E2DDE3C7400781124 /* NFCommands.swift in Sources */, A90D495E2DDE3C7400781124 /* NFCommands.swift in Sources */,
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 */, A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */,
A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */, A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */,
A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */, A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */,
A90D495B2DDE2EDB00781124 /* MacNearFutureApp.swift in Sources */, A90D495B2DDE2EDB00781124 /* MacNearFutureApp.swift in Sources */,
A91EF8082DFC8B8B00B8463D /* ColorCodable.swift in Sources */,
A90D49522DDE2D0000781124 /* Extensions.swift in Sources */, A90D49522DDE2D0000781124 /* Extensions.swift in Sources */,
A91EF81D2DFD796600B8463D /* SymbolsPicker.swift in Sources */,
A90D49422DDE114100781124 /* Events.swift in Sources */, A90D49422DDE114100781124 /* Events.swift in Sources */,
A90D49382DDE0FAF00781124 /* ContentViewMac.swift in Sources */, A90D49382DDE0FAF00781124 /* ContentViewMac.swift in Sources */,
A90D49622DDE626300781124 /* Settings.swift in Sources */, A90D49622DDE626300781124 /* Settings.swift in Sources */,
A95E9ED32DFC703200ED655F /* iCloudSettingsView.swift in Sources */,
A96609E72DFD800000DBFA78 /* HelpView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -534,15 +571,21 @@
A90D49612DDE626300781124 /* Settings.swift in Sources */, A90D49612DDE626300781124 /* Settings.swift in Sources */,
A90D495F2DDE3C7400781124 /* NFCommands.swift in Sources */, A90D495F2DDE3C7400781124 /* NFCommands.swift in Sources */,
A949F84F2DCAABE00064DCA0 /* EventListView.swift in Sources */, A949F84F2DCAABE00064DCA0 /* EventListView.swift in Sources */,
A91EF8092DFC8B8B00B8463D /* ColorCodable.swift in Sources */,
A949F8502DCAABE00064DCA0 /* HelpView.swift in Sources */, A949F8502DCAABE00064DCA0 /* HelpView.swift in Sources */,
A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */, A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */,
A914FA4D2DD2768900856265 /* WhatsNewView.swift in Sources */, A914FA4D2DD2768900856265 /* WhatsNewView.swift in Sources */,
A91EF80B2DFC910000B8463D /* ViewModifiers.swift in Sources */,
A949F8512DCAABE00064DCA0 /* ExportView.swift in Sources */, A949F8512DCAABE00064DCA0 /* ExportView.swift in Sources */,
A95E9ED82DFC742B00ED655F /* AccentIcon.swift in Sources */,
A90D49532DDE2D0000781124 /* Extensions.swift in Sources */, A90D49532DDE2D0000781124 /* Extensions.swift in Sources */,
A949F8522DCAABE00064DCA0 /* iCloudSettingsView.swift in Sources */, A949F8522DCAABE00064DCA0 /* iCloudSettingsView.swift in Sources */,
A9BAC6882DFF242300EC8E44 /* CompleteEventButton.swift in Sources */,
A949F8532DCAABE00064DCA0 /* ImportView.swift in Sources */, A949F8532DCAABE00064DCA0 /* ImportView.swift in Sources */,
A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */, A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */,
A91EF81E2DFD796600B8463D /* SymbolsPicker.swift in Sources */,
A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */, A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */,
A91EF81A2DFD77BF00B8463D /* SymbolsLoader.swift in Sources */,
A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */, A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -552,7 +595,12 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A979F6182D2714310094C0B3 /* Events.swift in Sources */, A979F6182D2714310094C0B3 /* Events.swift in Sources */,
A91EF81C2DFD796600B8463D /* SymbolsPicker.swift in Sources */,
A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */, A979F60A2D270AF00094C0B3 /* NearFutureWidgetsBundle.swift in Sources */,
A95E9EDA2DFC742B00ED655F /* AccentIcon.swift in Sources */,
A91EF80D2DFC910000B8463D /* ViewModifiers.swift in Sources */,
A91EF8182DFD77BF00B8463D /* SymbolsLoader.swift in Sources */,
A91EF8072DFC8B8B00B8463D /* ColorCodable.swift in Sources */,
A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */, A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */,
A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */, A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */,
A90D49632DDE626300781124 /* Settings.swift in Sources */, A90D49632DDE626300781124 /* Settings.swift in Sources */,
@@ -562,10 +610,6 @@
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */ /* Begin PBXTargetDependency section */
A90D494D2DDE2C6000781124 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = A90D494C2DDE2C6000781124 /* SFSymbolsPicker */;
};
A979F6132D270AF90094C0B3 /* PBXTargetDependency */ = { A979F6132D270AF90094C0B3 /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
platformFilter = ios; platformFilter = ios;
@@ -595,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)",
@@ -607,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;
}; };
@@ -626,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)",
@@ -638,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;
}; };
@@ -791,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;
@@ -819,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;
@@ -843,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;
@@ -868,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;
@@ -994,35 +1044,6 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/alessiorubicini/SFSymbolsPicker";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.6;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
A90D494C2DDE2C6000781124 /* SFSymbolsPicker */ = {
isa = XCSwiftPackageProductDependency;
package = A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */;
productName = SFSymbolsPicker;
};
A90D49552DDE2D5800781124 /* SFSymbolsPicker */ = {
isa = XCSwiftPackageProductDependency;
package = A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */;
productName = SFSymbolsPicker;
};
A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */ = {
isa = XCSwiftPackageProductDependency;
package = A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */;
productName = SFSymbolsPicker;
};
/* End XCSwiftPackageProductDependency section */
}; };
rootObject = A920C27C2D24011300E4F9B1 /* Project object */; rootObject = A920C27C2D24011300E4F9B1 /* Project object */;
} }

View File

@@ -1,15 +0,0 @@
{
"originHash" : "19df39f99b22f4ef95b73ed292ffb0c8d7694dd4c9db2b96ea73b091b7b1a026",
"pins" : [
{
"identity" : "sfsymbolspicker",
"kind" : "remoteSourceControl",
"location" : "https://github.com/alessiorubicini/SFSymbolsPicker",
"state" : {
"revision" : "73c909b8a7fc77a30dd04208e33f759f8b52c4c8",
"version" : "1.0.6"
}
}
],
"version" : 3
}

View File

@@ -11,7 +11,8 @@ struct ArchiveView: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@State var showAddEvent: Bool = false @State var showAddEvent: Bool = false
var filteredEvents: [Event] { var filteredEvents: [Event] {
return viewModel.events.filter() {$0.complete} let filteredEvents = viewModel.events.filter({$0.complete})
return filteredEvents.reversed()
} }
var body: some View { var body: some View {
NavigationStack { NavigationStack {
@@ -22,42 +23,48 @@ struct ArchiveView: View {
} else { } else {
ScrollView { ScrollView {
ForEach(filteredEvents) { event in ForEach(filteredEvents) { event in
NavigationLink() {
EditEventView(
viewModel: viewModel,
event: Binding(
get: { event },
set: { newValue in
viewModel.editEvent(newValue)
}
)
)
} label: {
EventListView(viewModel: viewModel, event: event) EventListView(viewModel: viewModel, event: event)
.id(event)
}
.transition(.moveAndFadeReversed) .transition(.moveAndFadeReversed)
.id(event.complete) .contextMenu() {
Button(role: .destructive) {
viewModel.removeEvent(event)
} label: {
Label("Delete", systemImage: "trash")
.tint(.red)
}
}
} }
.padding(.horizontal) .padding(.horizontal)
} }
.animation(.default, value: filteredEvents) .animation(.default, value: filteredEvents)
} }
} }
.transition(.opacity)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .primaryAction) {
AddEventButton(showingAddEventView: $showAddEvent) AddEventButton(showingAddEventView: $showAddEvent)
} }
} }
.navigationTitle("Archive") .navigationTitle("Archive")
.apply { .modifier(navigationInlineLarge())
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
} }
.sheet(isPresented: $showAddEvent) { .sheet(isPresented: $showAddEvent) {
AddEventView( AddEventView(
viewModel: viewModel, viewModel: viewModel
eventName: $viewModel.editableTemplate.name,
eventComplete: $viewModel.editableTemplate.complete,
eventCompleteDesc: $viewModel.editableTemplate.completeDesc,
eventSymbol: $viewModel.editableTemplate.symbol,
eventColor: $viewModel.editableTemplate.color.colorBind,
eventNotes: $viewModel.editableTemplate.notes,
eventDate: $viewModel.editableTemplate.date,
eventRecurrence: $viewModel.editableTemplate.recurrence,
adding: true
) )
} }
} }

View File

@@ -9,12 +9,10 @@ import SwiftUI
import UserNotifications import UserNotifications
import SwiftData import SwiftData
enum Field {
case Search
}
enum Tab { enum Tab {
case home case home
case archive case archive
case symbols
case stats case stats
case settings case settings
} }
@@ -22,10 +20,10 @@ enum Tab {
struct ContentView: View { struct ContentView: View {
@StateObject var viewModel: EventViewModel @StateObject var viewModel: EventViewModel
@StateObject var settingsModel: SettingsViewModel @StateObject var settingsModel: SettingsViewModel
@State var selection: Tab = .home @State var tabSelection: Tab = .home
var body: some View { var body: some View {
TabView(selection: $selection) { TabView(selection: $tabSelection) {
HomeView(viewModel: viewModel, settingsModel: settingsModel) HomeView(viewModel: viewModel, settingsModel: settingsModel)
.tabItem { .tabItem {
Label("Home", systemImage: "house") Label("Home", systemImage: "house")
@@ -36,7 +34,16 @@ struct ContentView: View {
Label("Archive", systemImage: "tray.full") Label("Archive", systemImage: "tray.full")
} }
.tag(Tab.archive) .tag(Tab.archive)
SymbolsPicker(
selection: .constant(""),
browsing: true
)
.tabItem {
Label("Symbols", systemImage: "star.circle")
}
.tag(Tab.symbols)
StatsView(viewModel: viewModel) StatsView(viewModel: viewModel)
// SymbolsPickerStoryboardUIViewRepresentable()
.tabItem { .tabItem {
Label("Statistics", systemImage: "chart.pie") Label("Statistics", systemImage: "chart.pie")
} }
@@ -47,11 +54,7 @@ struct ContentView: View {
} }
.tag(Tab.settings) .tag(Tab.settings)
} }
.apply { .modifier(hapticHeavy(trigger: tabSelection))
if #available(iOS 17, *) {
$0.sensoryFeedback(.impact(weight: .heavy, intensity: 1), trigger: selection)
}
}
.sheet(isPresented: $settingsModel.settings.showWhatsNew) { .sheet(isPresented: $settingsModel.settings.showWhatsNew) {
WhatsNewView(settingsModel: settingsModel) WhatsNewView(settingsModel: settingsModel)
} }

View File

@@ -6,26 +6,16 @@
// //
import SwiftUI import SwiftUI
import SFSymbolsPicker
struct AddEventView: View { struct AddEventView: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@Binding var eventName: String @State var event: Event = dummyEventViewModel().template
@Binding var eventComplete: Bool
@Binding var eventCompleteDesc: String
@Binding var eventSymbol: String
@Binding var eventColor: Color
@Binding var eventNotes: String
@Binding var eventDate: Date
@Binding var eventRecurrence: Event.RecurrenceType
@State var adding: Bool @State var adding: Bool = true
@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
@@ -33,13 +23,21 @@ struct AddEventView: View {
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
var isMac: Bool {
if #available(iOS 1, *) {
return false
} else {
return true
}
}
var body: some View { var body: some View {
ZStack { ZStack {
if !adding { if !adding {
backgroundGradient backgroundGradient
} }
NavigationStack { NavigationStack {
Form { List {
Section( Section(
header: header:
Text("Event Details") Text("Event Details")
@@ -51,66 +49,52 @@ struct AddEventView: View {
Button() { Button() {
isSymbolPickerPresented.toggle() isSymbolPickerPresented.toggle()
} label: { } label: {
Image(systemName: eventSymbol) Image(systemName: event.symbol)
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(eventColor) .foregroundStyle(event.color.color)
} }
.frame(width: 20) .frame(width: 20)
.buttonStyle(.borderless) .buttonStyle(.borderless)
.sheet(isPresented: $isSymbolPickerPresented) { .sheet(isPresented: $isSymbolPickerPresented) {
SymbolsPicker( SymbolsPicker(
selection: $eventSymbol, selection: $event.symbol
title: "Choose a Symbol", )
searchLabel: "Search...",
autoDismiss: true)
.presentationDetents([.medium]) .presentationDetents([.medium])
.apply { .modifier(presentationSizeForm())
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
}
ColorPicker("", selection: $eventColor, supportsOpacity: false)
.fixedSize()
ZStack {
TextField("Event Name", text: $eventName)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.trailing, eventName.isEmpty ? 0 : 30)
.animation(.spring, value: eventName)
.focused($focusedField, equals: Field.Name)
.submitLabel(.next)
.onSubmit {
focusedField = .Notes
}
// MagicClearButton(text: $eventName)
} }
TextField("Event Name", text: $event.name)
.textFieldStyle(.roundedBorder)
} }
// dscription // dscription
ZStack { ZStack {
TextField("Event Notes", text: $eventNotes) if event.notes.isEmpty {
.textFieldStyle(RoundedBorderTextFieldStyle()) HStack {
.padding(.trailing, eventNotes.isEmpty ? 0 : 30) Text("Event Notes")
.animation(.spring, value: eventNotes) .opacity(0.5)
.focused($focusedField, equals: Field.Notes) Spacer()
.submitLabel(.done)
.onSubmit {
focusedField = nil
} }
// MagicClearButton(text: $eventNotes) }
TextEditor(text: $event.notes)
.lineLimit(10)
} }
ColorPicker("Event Color", selection: $event.color.colorBind)
// date picker // date picker
HStack { HStack {
Spacer() Spacer()
DatePicker("", selection: $eventDate, displayedComponents: .date) DatePicker("", selection: $event.date, displayedComponents: .date)
// .datePickerStyle(datepickersty) #if os(iOS)
.datePickerStyle(.wheel)
#else
.datePickerStyle(.graphical)
#endif
Spacer() Spacer()
Button() { Button() {
eventDate = Date() event.date = Date()
} label: { } label: {
Image(systemName: "arrow.uturn.left") Image(systemName: "arrow.uturn.left")
.resizable() .resizable()
@@ -122,12 +106,15 @@ struct AddEventView: View {
DatePicker( DatePicker(
"", "",
selection: $eventDate, selection: $event.date,
displayedComponents: .hourAndMinute displayedComponents: .hourAndMinute
) )
#if os(macOS)
.datePickerStyle(.stepperField)
#endif
// re-ocurrence Picker // re-ocurrence Picker
Picker("Recurrence", selection: $eventRecurrence) { Picker("Recurrence", selection: $event.recurrence) {
ForEach(Event.RecurrenceType.allCases, id: \.self) { recurrence in ForEach(Event.RecurrenceType.allCases, id: \.self) { recurrence in
Text(recurrence.rawValue.capitalized) Text(recurrence.rawValue.capitalized)
} }
@@ -135,61 +122,45 @@ struct AddEventView: View {
.pickerStyle(SegmentedPickerStyle()) .pickerStyle(SegmentedPickerStyle())
Text( Text(
describeOccurrence( describeOccurrence(
date: eventDate, date: event.date,
recurrence: eventRecurrence recurrence: event.recurrence
) )
) )
} }
} }
.scrollContentBackground(.hidden)
.navigationTitle("\(adding ? "Add Event" : "")") .navigationTitle("\(adding ? "Add Event" : "")")
// .navigationBarTitleDisplayMode(.inline) .modifier(navigationInlineLarge())
.toolbar { .toolbar {
ToolbarItem(/*placement: .topBarLeading*/) { ToolbarItem(placement: .cancellationAction) {
if adding { if adding {
Button() { Button() {
resetAddEventView() resetAddEventView()
dismiss() dismiss()
} label: { } label: {
Image(systemName: "xmark.circle.fill") Image(systemName: "xmark")
.symbolRenderingMode(.hierarchical)
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 30) .tint(.one)
} }
} }
} }
ToolbarItem/*(placement: .topBarTrailing)*/ { ToolbarItem() {
if adding { if adding {
Button { Button {
viewModel.addEvent( viewModel.addEvent(
newEvent: Event( newEvent: event
name: eventName,
complete: eventComplete,
completeDesc: eventCompleteDesc,
symbol: eventSymbol,
color: ColorCodable(eventColor),
notes: eventNotes,
date: eventDate,
recurrence: eventRecurrence
) )
)
bye.toggle()
resetAddEventView() resetAddEventView()
#if canImport(UIKit)
UINotificationFeedbackGenerator().notificationOccurred(.success)
#endif
} label: { } label: {
Text("Save") Label("Save", systemImage: "checkmark")
.font(.headline)
.cornerRadius(10)
.buttonStyle(BorderedProminentButtonStyle())
} }
.apply { .tint(.accent)
if #available(iOS 17, *) { .disabled(event.name.isEmpty)
$0.sensoryFeedback(.success, trigger: bye)
}
}
.disabled(eventName.isEmpty)
.onTapGesture { .onTapGesture {
if eventName.isEmpty { if event.name.isEmpty {
showNeedsNameAlert.toggle() showNeedsNameAlert.toggle()
} }
} }
@@ -201,36 +172,32 @@ struct AddEventView: View {
} message: { } message: {
Text("Give your Event a name before saving.") Text("Give your Event a name before saving.")
} }
if eventName.isEmpty { }
HStack { }
Image(systemName: "exclamationmark") ToolbarItem(placement: .confirmationAction) {
.foregroundStyle(.red) if !adding {
Text("Give your event a name.") 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(isMac ? .automatic : .hidden)
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
.scrollContentBackground(.hidden)
}
.apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
} }
} }
func resetAddEventView() { func resetAddEventView() {
//reset addeventView //reset addeventView
eventName = viewModel.template.name event = viewModel.template
eventComplete = viewModel.template.complete
eventCompleteDesc = viewModel.template.completeDesc
eventSymbol = viewModel.template.symbol
eventColor = randomColor()
eventNotes = viewModel.template.notes
eventDate = viewModel.template.date
eventRecurrence = viewModel.template.recurrence
dismiss() dismiss()
} }
@@ -243,14 +210,6 @@ struct AddEventView: View {
.sheet(isPresented: .constant(true)) { .sheet(isPresented: .constant(true)) {
AddEventView( AddEventView(
viewModel: vm, viewModel: vm,
eventName: .constant(vm.template.notes),
eventComplete: .constant(vm.template.complete),
eventCompleteDesc: .constant(vm.template.completeDesc),
eventSymbol: .constant(vm.template.symbol),
eventColor: .constant(vm.template.color.color),
eventNotes: .constant(vm.template.notes),
eventDate: .constant(vm.template.date),
eventRecurrence: .constant(vm.template.recurrence),
adding: true adding: true
) )
} }

View File

@@ -12,44 +12,13 @@ struct EditEventView: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@Binding var event: Event @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 { var body: some View {
AddEventView( AddEventView(
viewModel: viewModel, viewModel: viewModel,
eventName: $event.name, event: event,
eventComplete: $event.complete,
eventCompleteDesc: $event.completeDesc,
eventSymbol: $event.symbol,
eventColor: $event.color.colorBind,
eventNotes: $event.notes,
eventDate: $event.date,
eventRecurrence: $event.recurrence,
adding: false //bc we editing existing event adding: false //bc we editing existing event
) )
.navigationTitle("Edit Event") .navigationTitle("Edit Event")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button() {
saveEdits()
} label: {
Text("Done")
.bold()
}
.disabled(event.name == "")
}
}
} }
} }

View File

@@ -12,14 +12,87 @@ struct EventListView: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@State var event: Event @State var event: Event
@Environment(\.openWindow) var openWindow
@State var hovering: Bool = false
#if canImport(AppKit)
var body: some View { var body: some View {
NavigationLink() { ZStack {
EditEventView( 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)
}
CompleteEventButton(
viewModel: viewModel, viewModel: viewModel,
event: $event event: $event
) )
} label: { }
ZStack { .fixedSize(horizontal: false, vertical: true)
}
.onHover { isHovering in
withAnimation {
hovering.toggle()
}
}
.onTapGesture {
openWindow(value: event.id)
}
}
#else
var body: some View {
HStack { HStack {
RoundedRectangle(cornerRadius: 5) RoundedRectangle(cornerRadius: 5)
.frame(width: 7) .frame(width: 7)
@@ -78,91 +151,32 @@ struct EventListView: View {
.foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one) .foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one)
.multilineTextAlignment(.trailing) .multilineTextAlignment(.trailing)
} }
Button() { CompleteEventButton(
withAnimation { viewModel: viewModel,
event.complete.toggle() event: $event
)
} }
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)
.apply {
if #available(iOS 17, *) {
$0.sensoryFeedback(.success, trigger: event.complete)
}
}
}
.transition(.opacity)
.padding(.vertical, 5) .padding(.vertical, 5)
.background(.ultraThinMaterial)
.overlay( .overlay(
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: 15)
.stroke( .stroke(.one.opacity(0.5), lineWidth: 1)
.one.opacity(0.5),
lineWidth: 1
)
)
.clipShape(
RoundedRectangle(cornerRadius: 10)
) )
.clipShape(RoundedRectangle(cornerRadius: 15))
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
} .swipeActions(edge: .trailing, allowsFullSwipe: true) {
.contextMenu() {
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")
} }
} }
} }
} #endif
} }
#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

@@ -13,6 +13,10 @@ enum HelpType {
case Archive case Archive
} }
enum Field {
case Search
}
struct HelpView: View { struct HelpView: View {
/// initialises a Search HelpView /// initialises a Search HelpView
/// ///
@@ -74,7 +78,7 @@ struct HelpView: View {
var body: some View { var body: some View {
List { List {
ZStack { ZStack {
Color(.tintColor) Color(.accent)
.opacity(0.4) .opacity(0.4)
.padding(.horizontal, -15) .padding(.horizontal, -15)
.blur(radius: 5) .blur(radius: 5)

View File

@@ -5,19 +5,13 @@
// Created by neon443 on 12/05/2025. // Created by neon443 on 12/05/2025.
// //
import SwiftUI;import AppIntents import SwiftUI
import AppIntents
struct HomeView: View { struct HomeView: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@ObservedObject var settingsModel: SettingsViewModel @ObservedObject var settingsModel: SettingsViewModel
@State private var eventName: String = ""
@State private var eventComplete: Bool = false
@State private var eventCompleteDesc: String = ""
@State private var eventSymbol: String = "star"
@State private var eventColor: Color = randomColor()
@State private var eventNotes: String = ""
@State private var eventDate = Date()
@State private var eventRecurrence: Event.RecurrenceType = .none
@State private var showingAddEventView: Bool = false @State private var showingAddEventView: Bool = false
@State private var searchInput: String = "" @State private var searchInput: String = ""
@Environment(\.colorScheme) var appearance @Environment(\.colorScheme) var appearance
@@ -47,36 +41,38 @@ struct HomeView: View {
ZStack { ZStack {
backgroundGradient backgroundGradient
VStack { VStack {
ZStack {
TextField(
"\(Image(systemName: "magnifyingglass")) Search",
text: $searchInput
)
.padding(.trailing, searchInput.isEmpty ? 0 : 30)
.animation(.spring, value: searchInput)
.textFieldStyle(RoundedBorderTextFieldStyle())
.submitLabel(.done)
.focused($focusedField, equals: Field.Search)
.onSubmit {
focusedField = nil
}
MagicClearButton(text: $searchInput)
.onTapGesture {
focusedField = nil
}
}
.padding(.horizontal)
if filteredEvents.isEmpty && !searchInput.isEmpty { if filteredEvents.isEmpty && !searchInput.isEmpty {
HelpView(searchInput: $searchInput, focusedField: focusedField) HelpView(searchInput: $searchInput, focusedField: focusedField)
} else { } else {
ScrollView { ScrollView {
// LazyVStack {
ForEach(filteredEvents) { event in ForEach(filteredEvents) { event in
NavigationLink() {
EditEventView(
viewModel: viewModel,
event: Binding(
get: { event },
set: { newValue in
viewModel.editEvent(newValue)
}
)
)
} label: {
EventListView(viewModel: viewModel, event: event) EventListView(viewModel: viewModel, event: event)
.id(event)
}
.transition(.moveAndFade) .transition(.moveAndFade)
.id(event.complete) .contextMenu() {
Button(role: .destructive) {
viewModel.removeEvent(event)
} label: {
Label("Delete", systemImage: "trash")
.tint(.red)
}
}
} }
.padding(.horizontal) .padding(.horizontal)
// }
if filteredEvents.isEmpty { if filteredEvents.isEmpty {
HelpView( HelpView(
searchInput: $searchInput, searchInput: $searchInput,
@@ -88,30 +84,16 @@ struct HomeView: View {
.animation(.default, value: filteredEvents) .animation(.default, value: filteredEvents)
} }
} }
.searchable(text: $searchInput)
.navigationTitle("Near Future") .navigationTitle("Near Future")
.apply { .modifier(navigationInlineLarge())
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
.sheet(isPresented: $showingAddEventView) { .sheet(isPresented: $showingAddEventView) {
AddEventView( AddEventView(
viewModel: viewModel, viewModel: viewModel
eventName: $eventName,
eventComplete: $eventComplete,
eventCompleteDesc: $eventCompleteDesc,
eventSymbol: $eventSymbol,
eventColor: $eventColor,
eventNotes: $eventNotes,
eventDate: $eventDate,
eventRecurrence: $eventRecurrence,
adding: true //adding event
) )
} }
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .primaryAction) {
AddEventButton(showingAddEventView: $showingAddEventView) AddEventButton(showingAddEventView: $showingAddEventView)
} }
} }

View File

@@ -34,17 +34,12 @@ struct AddEventButton: View {
Button() { Button() {
showingAddEventView.toggle() showingAddEventView.toggle()
} label: { } label: {
ZStack {
Circle()
.frame(width: 33)
.foregroundStyle(.one)
Image(systemName: "plus") Image(systemName: "plus")
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 15) .frame(width: 15)
.bold() .bold()
.foregroundStyle(.two) .foregroundStyle(.one)
}
} }
} }
} }

View File

@@ -12,7 +12,11 @@ struct ExportView: View {
var body: some View { var body: some View {
List { List {
Button() { Button() {
#if canImport(UIKit)
UIPasteboard.general.string = viewModel.exportEvents() UIPasteboard.general.string = viewModel.exportEvents()
#else
NSPasteboard.general.setString(viewModel.exportEvents(), forType: .string)
#endif
} label: { } label: {
Label("Copy Events", systemImage: "document.on.clipboard") Label("Copy Events", systemImage: "document.on.clipboard")
} }

View File

@@ -66,66 +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()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.foregroundStyle(replaceCurrentEvents ? .red.opacity(0.25) : .black.opacity(0.2))
.animation(.default, value: replaceCurrentEvents)
.ignoresSafeArea()
ZStack {
Rectangle()
.background(.ultraThinMaterial)
.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() importEvents()
} label: { } label: {
Text("cancel") Text("Replace Events")
.font(.title2)
.bold()
} }
.buttonStyle(BorderedProminentButtonStyle())
Spacer()
Button() { Button() {
withAnimation {
showAlert.toggle()
}
importEvents() importEvents()
} label: { } label: {
Text("yes") Text("Add to Events")
.font(.title2) .foregroundStyle(.one)
.bold()
} }
.buttonStyle(BorderedProminentButtonStyle())
} }
.padding()
}
.padding()
}
.frame(maxWidth: 250, maxHeight: 250)
}
.opacity(showAlert ? 1 : 0)
} }
} }
} }

View File

@@ -11,47 +11,20 @@ struct SettingsView: View {
@ObservedObject var viewModel: EventViewModel @ObservedObject var viewModel: EventViewModel
@ObservedObject var settingsModel: SettingsViewModel @ObservedObject var settingsModel: SettingsViewModel
@State private var hasUbiquitous: Bool = false
@State private var lastSyncWasSuccessful: Bool = false
@State private var lastSyncWasNormalAgo: Bool = false
@State private var localCountEqualToiCloud: Bool = false
@State private var icloudCountEqualToLocal: Bool = false
@State private var importStr: String = "" @State private var importStr: String = ""
func updateStatus() { func changeIcon(to toIcon: String) {
let vm = viewModel
hasUbiquitous = vm.hasUbiquitousKeyValueStore()
lastSyncWasSuccessful = vm.syncStatus.contains("Success")
lastSyncWasNormalAgo = vm.lastSync?.timeIntervalSinceNow.isNormal ?? false
localCountEqualToiCloud = vm.localEventCount == vm.icloudEventCount
icloudCountEqualToLocal = vm.icloudEventCount == vm.localEventCount
}
var iCloudStatusColor: Color {
let allTrue = hasUbiquitous && lastSyncWasSuccessful && lastSyncWasNormalAgo && localCountEqualToiCloud && icloudCountEqualToLocal
let someTrue = hasUbiquitous || lastSyncWasSuccessful || lastSyncWasNormalAgo || localCountEqualToiCloud || icloudCountEqualToLocal
if allTrue {
return .green
} else if someTrue {
return .orange
} else {
return .red
}
}
func changeIcon(to: String) {
guard UIApplication.shared.supportsAlternateIcons else { guard UIApplication.shared.supportsAlternateIcons else {
print("doesnt tsupport alternate icons") print("doesnt tsupport alternate icons")
return return
} }
guard to != "orange" else { guard toIcon != "orange" else {
UIApplication.shared.setAlternateIconName(nil) { error in UIApplication.shared.setAlternateIconName(nil) { error in
print(error as Any) print(error as Any)
} }
return return
} }
UIApplication.shared.setAlternateIconName(to) { error in UIApplication.shared.setAlternateIconName(toIcon) { error in
print(error as Any) print(error as Any)
} }
} }
@@ -74,6 +47,7 @@ struct SettingsView: View {
.foregroundStyle(color) .foregroundStyle(color)
.frame(width: 30) .frame(width: 30)
} }
.buttonStyle(.plain)
if ColorCodable(color) == settingsModel.settings.tint { if ColorCodable(color) == settingsModel.settings.tint {
let needContrast: Bool = ColorCodable(color) == settingsModel.settings.tint let needContrast: Bool = ColorCodable(color) == settingsModel.settings.tint
Circle() Circle()
@@ -94,6 +68,8 @@ struct SettingsView: View {
NavigationLink() { NavigationLink() {
List { List {
if !settingsModel.notifsGranted { if !settingsModel.notifsGranted {
Text("\(Image(systemName: "xmark")) Notifications disabled for Near Future")
.foregroundStyle(.red)
Button("Request Notifications") { Button("Request Notifications") {
Task.detached { Task.detached {
let requestNotifsResult = await requestNotifs() let requestNotifsResult = await requestNotifs()
@@ -102,8 +78,6 @@ struct SettingsView: View {
} }
} }
} }
Text("\(Image(systemName: "xmark")) Notifications disabled for Near Future")
.foregroundStyle(.red)
} else { } else {
Text("\(Image(systemName: "checkmark")) Notifications enabled for Near Future") Text("\(Image(systemName: "checkmark")) Notifications enabled for Near Future")
.foregroundStyle(.green) .foregroundStyle(.green)
@@ -116,13 +90,7 @@ struct SettingsView: View {
NavigationLink() { NavigationLink() {
iCloudSettingsView( iCloudSettingsView(
viewModel: viewModel, viewModel: viewModel,
settingsModel: settingsModel, settingsModel: settingsModel
hasUbiquitous: $hasUbiquitous,
lastSyncWasSuccessful: $lastSyncWasSuccessful,
lastSyncWasNormalAgo: $lastSyncWasNormalAgo,
localCountEqualToiCloud: $localCountEqualToiCloud,
icloudCountEqualToLocal: $icloudCountEqualToLocal,
updateStatus: updateStatus
) )
} label: { } label: {
HStack { HStack {
@@ -131,12 +99,12 @@ struct SettingsView: View {
Spacer() Spacer()
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(iCloudStatusColor) .foregroundStyle(viewModel.iCloudStatusColor)
} }
} }
.onAppear { .onAppear {
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
NavigationLink() { NavigationLink() {
ImportView(viewModel: viewModel, importStr: $importStr) ImportView(viewModel: viewModel, importStr: $importStr)
@@ -180,15 +148,9 @@ struct SettingsView: View {
} }
} }
} }
.scrollContentBackground(.hidden)
.navigationTitle("Settings") .navigationTitle("Settings")
.apply { .modifier(navigationInlineLarge())
if #available(iOS 17, *) { .scrollContentBackground(.hidden)
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
} }
} }
} }

View File

@@ -19,6 +19,11 @@ struct WhatsNewView: View {
@State var bye: Bool = false @State var bye: Bool = false
var whatsNewChunks: [WhatsNewChunk] { var whatsNewChunks: [WhatsNewChunk] {
return [ return [
WhatsNewChunk(
symbol: "desktopcomputer",
title: "Mac Native App",
subtitle: "New Mac native app (Intel too!)"
),
WhatsNewChunk( WhatsNewChunk(
symbol: "iphone.radiowaves.left.and.right", symbol: "iphone.radiowaves.left.and.right",
title: "Haptic Feedback", title: "Haptic Feedback",
@@ -53,13 +58,11 @@ struct WhatsNewView: View {
} }
var body: some View { var body: some View {
NavigationStack { NavigationStack {
List { ScrollView {
VStack {
Text("What's New") Text("What's New")
.font(.largeTitle) .font(.largeTitle)
.bold() .bold()
AboutView() .padding(.vertical)
Divider()
VStack(alignment: .leading) { VStack(alignment: .leading) {
ForEach(whatsNewChunks) { new in ForEach(whatsNewChunks) { new in
WhatsNewChunkView( WhatsNewChunkView(
@@ -68,8 +71,9 @@ struct WhatsNewView: View {
subtitle: new.subtitle subtitle: new.subtitle
) )
} }
Spacer()
} }
} .padding(.horizontal, 10)
} }
Button() { Button() {
bye.toggle() bye.toggle()
@@ -79,24 +83,14 @@ struct WhatsNewView: View {
.font(.headline) .font(.headline)
.frame(height: 40) .frame(height: 40)
.bold() .bold()
.frame(maxWidth: .infinity) // .frame(maxWidth: .infinity)
}
.buttonStyle(BorderedProminentButtonStyle())
.clipShape(RoundedRectangle(cornerRadius: 15))
.padding().padding()
.apply {
if #available(iOS 17, *) {
$0.sensoryFeedback(.impact(weight: .heavy, intensity: 1), trigger: bye)
}
} }
.foregroundStyle(.orange)
.modifier(glassButton())
.modifier(hapticHeavy(trigger: bye))
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
.apply {
if #available(iOS 16.4, *) {
$0.presentationBackground(.ultraThinMaterial)
}
}
.onDisappear { .onDisappear {
settingsModel.settings.prevAppVersion = getVersion()+getBuildID() settingsModel.settings.prevAppVersion = getVersion()+getBuildID()
settingsModel.saveSettings() settingsModel.saveSettings()
@@ -122,7 +116,7 @@ struct WhatsNewChunkView: View {
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 30, height: 30) .frame(width: 30, height: 30)
.foregroundStyle(Color.accentColor) .foregroundStyle(Color.orange)
.padding(.trailing, 15) .padding(.trailing, 15)
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(title) Text(title)

View File

@@ -13,13 +13,13 @@ struct iCloudSettingsView: View {
@State var showPushAlert: Bool = false @State var showPushAlert: Bool = false
@State var showPullAlert: Bool = false @State var showPullAlert: Bool = false
@Binding var hasUbiquitous: Bool // @Binding var hasUbiquitous: Bool
@Binding var lastSyncWasSuccessful: Bool // @Binding var lastSyncWasSuccessful: Bool
@Binding var lastSyncWasNormalAgo: Bool // @Binding var lastSyncWasNormalAgo: Bool
@Binding var localCountEqualToiCloud: Bool // @Binding var localCountEqualToiCloud: Bool
@Binding var icloudCountEqualToLocal: Bool // @Binding var icloudCountEqualToLocal: Bool
//
var updateStatus: () -> Void // var updateStatus: () -> Void
var body: some View { var body: some View {
ZStack { ZStack {
@@ -55,7 +55,7 @@ struct iCloudSettingsView: View {
Button("OK", role: .destructive) { Button("OK", role: .destructive) {
viewModel.replaceiCloudWithLocalData() viewModel.replaceiCloudWithLocalData()
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
Button("Cancel", role: .cancel) {} Button("Cancel", role: .cancel) {}
} message: { } message: {
@@ -64,7 +64,7 @@ struct iCloudSettingsView: View {
Button() { Button() {
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} label: { } label: {
Image(systemName: "arrow.triangle.2.circlepath") Image(systemName: "arrow.triangle.2.circlepath")
.resizable() .resizable()
@@ -87,7 +87,7 @@ struct iCloudSettingsView: View {
Button("OK", role: .destructive) { Button("OK", role: .destructive) {
viewModel.replaceLocalWithiCloudData() viewModel.replaceLocalWithiCloudData()
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
Button("Cancel", role: .cancel) {} Button("Cancel", role: .cancel) {}
} message: { } message: {
@@ -112,23 +112,23 @@ struct iCloudSettingsView: View {
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.onAppear { .onAppear {
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(hasUbiquitous ? .green : .red) .foregroundStyle(viewModel.hasUbiquitous ? .green : .red)
Text("iCloud") Text("iCloud")
Spacer() Spacer()
Text("\(hasUbiquitous ? "" : "Not ")Working") Text("\(viewModel.hasUbiquitous ? "" : "Not ")Working")
.bold() .bold()
} }
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(lastSyncWasSuccessful ? .green : .red) .foregroundStyle(viewModel.lastSyncWasSuccessful ? .green : .red)
Text("Sync Status") Text("Sync Status")
Spacer() Spacer()
Text("\(viewModel.syncStatus)") Text("\(viewModel.syncStatus)")
@@ -138,7 +138,7 @@ struct iCloudSettingsView: View {
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(lastSyncWasNormalAgo ? .green : .red) .foregroundStyle(viewModel.lastSyncWasNormalAgo ? .green : .red)
Text("Last Sync") Text("Last Sync")
Spacer() Spacer()
Text("\(viewModel.lastSync?.formatted(date: .long, time: .standard) ?? "Never")") Text("\(viewModel.lastSync?.formatted(date: .long, time: .standard) ?? "Never")")
@@ -148,7 +148,7 @@ struct iCloudSettingsView: View {
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(localCountEqualToiCloud ? .green : .red) .foregroundStyle(viewModel.localCountEqualToiCloud ? .green : .red)
Text("Local Events") Text("Local Events")
Spacer() Spacer()
Text("\(viewModel.localEventCount)") Text("\(viewModel.localEventCount)")
@@ -158,7 +158,7 @@ struct iCloudSettingsView: View {
HStack { HStack {
Circle() Circle()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(icloudCountEqualToLocal ? .green : .red) .foregroundStyle(viewModel.icloudCountEqualToLocal ? .green : .red)
Text("Events in iCloud") Text("Events in iCloud")
Spacer() Spacer()
Text("\(viewModel.icloudEventCount)") Text("\(viewModel.icloudEventCount)")
@@ -172,11 +172,10 @@ struct iCloudSettingsView: View {
} }
.refreshable { .refreshable {
viewModel.sync() viewModel.sync()
updateStatus() viewModel.updateiCStatus()
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.navigationTitle("iCloud") .navigationTitle("iCloud")
.navigationBarTitleDisplayMode(.inline)
} }
} }
} }
@@ -184,12 +183,6 @@ struct iCloudSettingsView: View {
#Preview("iCloudSettingsView") { #Preview("iCloudSettingsView") {
iCloudSettingsView( iCloudSettingsView(
viewModel: dummyEventViewModel(), viewModel: dummyEventViewModel(),
settingsModel: dummySettingsViewModel(), settingsModel: dummySettingsViewModel()
hasUbiquitous: .constant(true),
lastSyncWasSuccessful: .constant(true),
lastSyncWasNormalAgo: .constant(true),
localCountEqualToiCloud: .constant(true),
icloudCountEqualToLocal: .constant(true),
updateStatus: {}
) )
} }

View File

@@ -58,13 +58,7 @@ struct StatsView: View {
} }
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.navigationTitle("Statistics") .navigationTitle("Statistics")
.apply { .modifier(navigationInlineLarge())
if #available(iOS 17, *) {
$0.toolbarTitleDisplayMode(.inlineLarge)
} else {
$0.navigationBarTitleDisplayMode(.inline)
}
}
} }
} }
} }

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.
@@ -11,17 +37,18 @@
- [x] Event colors - [x] Event colors
- [x] Recurrence - [x] Recurrence
- [x] Search - [x] Search
- [ ] Notifications - [x] Notifications
- [ ] Mac App
- [ ] Apple Watch App - [ ] Apple Watch App
- [x] Home Screen Widgets - [x] Home Screen Widgets
- [ ] Lock Screen Widgets - [ ] Lock Screen Widgets
- [ ] Later Box - [ ] Later Box
- [ ] Sort by - [ ] Sort by
- [ ] Reorder Events - [ ] Reorder Events
- [ ] Archive - [x] Archive
- [ ] Collaboration - [ ] Collaboration
- [ ] Autocomplete tasks - [ ] Autocomplete tasks
- [ ] Settings - [x] Settings
## Features ## Features
- **Event Creation**: Create events with a name, description, date, recurrence, and an icon - **Event Creation**: Create events with a name, description, date, recurrence, and an icon

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View File

@@ -0,0 +1,50 @@
{
"fill" : {
"linear-gradient" : [
"display-p3:0.12307,0.73184,0.43944,1.00000",
"display-p3:0.12142,0.28022,0.58006,1.00000"
]
},
"groups" : [
{
"layers" : [
{
"fill" : {
"automatic-gradient" : "display-p3:1.00000,0.30347,0.00000,1.00000"
},
"image-name" : "Rounded Rectangle.png",
"name" : "Rounded Rectangle",
"position" : {
"scale" : 1,
"translation-in-points" : [
-466.2265625,
37.9921875
]
}
},
{
"fill" : {
"automatic-gradient" : "display-p3:0.96611,0.36599,0.00000,1.00000"
},
"hidden" : false,
"image-name" : "Oval copy.png",
"name" : "Oval copy"
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}

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

@@ -17,21 +17,19 @@ extension View {
) )
.ignoresSafeArea(.all) .ignoresSafeArea(.all)
} }
func apply<V: View>(@ViewBuilder _ block: (Self) -> V) -> V { block(self) }
} }
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

@@ -0,0 +1,47 @@
//
// AccentIcon.swift
// NearFuture
//
// Created by neon443 on 13/06/2025.
//
import Foundation
import SwiftUI
#if canImport(AppKit)
import AppKit
#endif
class AccentIcon {
#if canImport(UIKit)
var icon: UIImage
#elseif canImport(AppKit)
var icon: NSImage
#endif
var color: Color
var name: String
init(_ colorName: String) {
#if canImport(UIKit)
self.icon = UIImage(named: "AppIcon")!
self.color = Color(uiColor: UIColor(named: "uiColors/\(colorName)")!)
#elseif canImport(AppKit)
self.icon = NSImage(imageLiteralResourceName: "AppIcon")
self.color = Color(nsColor: NSColor(named: "uiColors/\(colorName)")!)
#endif
self.name = colorName
if colorName != "orange" {
setSelfIcon(to: colorName)
}
}
func setSelfIcon(to name: String) {
#if canImport(UIKit)
self.icon = UIImage(named: name)!
#elseif canImport(AppKit)
self.icon = NSImage(imageLiteralResourceName: name)
#endif
}
}

View File

@@ -0,0 +1,83 @@
//
// ColorCodable.swift
// NearFuture
//
// Created by neon443 on 13/06/2025.
//
import Foundation
import SwiftUI
#if canImport(UIKit)
import UIKit
#else
import AppKit
#endif
struct ColorCodable: Codable, Equatable, Hashable {
init(_ color: Color) {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1
#if canImport(UIKit)
let uiColor = UIColor(color)
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
#elseif canImport(AppKit)
let nscolor = NSColor(color).usingColorSpace(.deviceRGB)
nscolor!.getRed(&r, green: &g, blue: &b, alpha: &a)
#endif
self = ColorCodable(
red: r,
green: g,
blue: b
)
}
#if canImport(UIKit)
init(uiColor: UIColor) {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
self = ColorCodable(
red: r,
green: g,
blue: b
)
}
#elseif canImport(AppKit)
init(nsColor: NSColor) {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
let nsColor = nsColor.usingColorSpace(.deviceRGB)
nsColor!.getRed(&r, green: &g, blue: &b, alpha: &a)
self = ColorCodable(
red: r,
green: g,
blue: b
)
}
#endif
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
var red: Double
var green: Double
var blue: Double
var color: Color {
Color(red: red, green: green, blue: blue)
}
var colorBind: Color {
get {
return Color(
red: red,
green: green,
blue: blue
)
} set {
let cc = ColorCodable(newValue)
self.red = cc.red
self.green = cc.green
self.blue = cc.blue
}
}
}

View File

@@ -11,10 +11,8 @@ import SwiftUI
import WidgetKit import WidgetKit
import UserNotifications import UserNotifications
import AppIntents import AppIntents
import AudioToolbox
#if canImport(AppKit) #if canImport(AppKit)
import AppKit import AppKit
import IOKit
#endif #endif
//@Model //@Model
@@ -26,7 +24,7 @@ import IOKit
// } // }
//} //}
struct Event: Identifiable, Codable, Equatable, Animatable { struct Event: Identifiable, Codable, Equatable, Animatable, Hashable {
var id = UUID() var id = UUID()
var name: String var name: String
var complete: Bool var complete: Bool
@@ -42,75 +40,6 @@ struct Event: Identifiable, Codable, Equatable, Animatable {
} }
} }
struct ColorCodable: Codable, Equatable {
init(_ color: Color) {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1
#if canImport(UIKit)
let uiColor = UIColor(color)
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
#elseif canImport(AppKit)
let nscolor = NSColor(color).usingColorSpace(.deviceRGB)
nscolor!.getRed(&r, green: &g, blue: &b, alpha: &a)
#endif
self = ColorCodable(
red: r,
green: g,
blue: b
)
}
#if canImport(UIKit)
init(uiColor: UIColor) {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
uiColor.getRed(&r, green: &g, blue: &b, alpha: &a)
self = ColorCodable(
red: r,
green: g,
blue: b
)
}
#elseif canImport(AppKit)
init(nsColor: NSColor) {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0
let nsColor = nsColor.usingColorSpace(.deviceRGB)
nsColor!.getRed(&r, green: &g, blue: &b, alpha: &a)
self = ColorCodable(
red: r,
green: g,
blue: b
)
}
#endif
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
var red: Double
var green: Double
var blue: Double
var color: Color {
Color(red: red, green: green, blue: blue)
}
var colorBind: Color {
get {
return Color(
red: red,
green: green,
blue: blue
)
} set {
let cc = ColorCodable(newValue)
self.red = cc.red
self.green = cc.green
self.blue = cc.blue
}
}
}
func daysUntilEvent(_ eventDate: Date) -> (long: String, short: String) { func daysUntilEvent(_ eventDate: Date) -> (long: String, short: String) {
let calendar = Calendar.current let calendar = Calendar.current
let startOfDayNow = calendar.startOfDay(for: Date()) let startOfDayNow = calendar.startOfDay(for: Date())
@@ -164,6 +93,25 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
@Published var localEventCount: Int = 0 @Published var localEventCount: Int = 0
@Published var syncStatus: String = "Not Synced" @Published var syncStatus: String = "Not Synced"
@Published var hasUbiquitous: Bool = false
@Published var lastSyncWasSuccessful: Bool = false
@Published var lastSyncWasNormalAgo: Bool = false
@Published var localCountEqualToiCloud: Bool = false
@Published var icloudCountEqualToLocal: Bool = false
var iCloudStatusColor: Color {
let allTrue = hasUbiquitous && lastSyncWasSuccessful && lastSyncWasNormalAgo && localCountEqualToiCloud && icloudCountEqualToLocal
let someTrue = hasUbiquitous || lastSyncWasSuccessful || lastSyncWasNormalAgo || localCountEqualToiCloud || icloudCountEqualToLocal
if allTrue {
return .green
} else if someTrue {
return .orange
} else {
return .red
}
}
init(load: Bool = true) { init(load: Bool = true) {
self.editableTemplate = template self.editableTemplate = template
if load { if load {
@@ -212,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)
} }
} }
@@ -234,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
@@ -274,8 +234,31 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
saveEvents() //sync with icloud saveEvents() //sync with icloud
} }
func removeEvent(at index: IndexSet) { func editEvent(_ editedEvent: Event) {
events.remove(atOffsets: index) 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 saveEvents() //sync local and icl
} }
@@ -343,6 +326,14 @@ class EventViewModel: ObservableObject, @unchecked Sendable {
} }
} }
func updateiCStatus() {
hasUbiquitous = hasUbiquitousKeyValueStore()
lastSyncWasSuccessful = syncStatus.contains("Success")
lastSyncWasNormalAgo = lastSync?.timeIntervalSinceNow.isNormal ?? false
localCountEqualToiCloud = localEventCount == icloudEventCount
icloudCountEqualToLocal = icloudEventCount == localEventCount
}
//MARK: Danger Zone //MARK: Danger Zone
func dangerClearLocalData() { func dangerClearLocalData() {
UserDefaults.standard.removeObject(forKey: "events") UserDefaults.standard.removeObject(forKey: "events")
@@ -516,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
@@ -533,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,40 +18,7 @@ struct NFSettings: Codable, Equatable {
var prevAppVersion: String = getVersion()+getBuildID() var prevAppVersion: String = getVersion()+getBuildID()
} }
class AccentIcon { @MainActor
#if canImport(UIKit)
var icon: UIImage
#elseif canImport(AppKit)
var icon: NSImage
#endif
var color: Color
var name: String
init(_ colorName: String) {
#if canImport(UIKit)
self.icon = UIImage(named: "AppIcon")!
self.color = Color(uiColor: UIColor(named: "uiColors/\(colorName)")!)
#elseif canImport(AppKit)
self.icon = NSImage(imageLiteralResourceName: "AppIcon")
self.color = Color(nsColor: NSColor(named: "uiColors/\(colorName)")!)
#endif
self.name = colorName
if colorName != "orange" {
setSelfIcon(to: colorName)
}
}
func setSelfIcon(to name: String) {
#if canImport(UIKit)
self.icon = UIImage(named: name)!
#elseif canImport(AppKit)
self.icon = NSImage(imageLiteralResourceName: name)
#endif
}
}
class SettingsViewModel: ObservableObject { class SettingsViewModel: ObservableObject {
@Published var settings: NFSettings = NFSettings() @Published var settings: NFSettings = NFSettings()
@@ -70,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

@@ -0,0 +1,36 @@
//
// SymbolsLoader.swift
// NearFuture
//
// Created by neon443 on 14/06/2025.
//
import Foundation
class SymbolsLoader: ObservableObject {
private var allSymbols: [String] = []
init() {
self.allSymbols = getAllSymbols()
}
func getSymbols(_ searched: String) -> [String] {
if searched.isEmpty {
return []
} else {
return allSymbols.filter() { $0.localizedCaseInsensitiveContains(searched) }
}
}
func getAllSymbols() -> [String] {
var allSymbols = [String]()
if let bundle = Bundle(identifier: "com.apple.CoreGlyphs"),
let resPath = bundle.path(forResource: "name_availability", ofType: "plist"),
let plist = try? NSDictionary(contentsOf: URL(fileURLWithPath: resPath), error: ()),
let plistSymbols = plist["symbols"] as? [String: String]
{
allSymbols = Array(plistSymbols.keys)
}
return allSymbols
}
}

View File

@@ -0,0 +1,99 @@
//
// SymbolsPicker.swift
// NearFuture
//
// Created by neon443 on 14/06/2025.
//
import SwiftUI
struct SymbolsPicker: View {
@StateObject private var symbolsLoader = SymbolsLoader()
@Binding var selection: String
@FocusState var searchfocuesd: Bool
@State var searchInput: String = ""
@State var browsing: Bool = false
@Environment(\.dismiss) var dismiss
var symbols: [String] {
return symbolsLoader.getSymbols(searchInput)
}
private func gridLayout(forWidth geoSizeWidth: CGFloat) -> [GridItem] {
let gridItem = GridItem(.fixed(80), spacing: 20, alignment: .center)
let columns = Int(geoSizeWidth/100.rounded(.up))
return Array(repeating: gridItem, count: columns)
}
var body: some View {
NavigationStack {
GeometryReader { geo in
ScrollView {
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()
.frame(width: 30)
Text("You look lost")
.font(.title)
.bold()
}
.padding()
Text("The symbol picker search only works with exact matches, try a different search term.")
}
LazyVGrid(columns: gridLayout(forWidth: geo.size.width)) {
ForEach(symbols, id: \.self) { symbol in
Button() {
selection = symbol
searchInput = ""
dismiss()
} label: {
VStack {
Image(systemName: symbol)
.resizable()
.scaledToFit()
.symbolRenderingMode(.palette)
.foregroundStyle(.blue, .gray, .black)
Text(symbol)
.truncationMode(.middle)
.font(.footnote)
}
}
.frame(maxWidth: 80, maxHeight: 80)
.buttonStyle(.plain)
}
}
}
}
.searchable(text: $searchInput)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
if !browsing {
Button() {
searchInput = ""
dismiss()
} label: {
Label("Cancel", systemImage: "xmark")
}
}
}
}
}
}
}
#Preview {
SymbolsPicker(selection: .constant(""))
}

View File

@@ -0,0 +1,77 @@
//
// ViewModifiers.swift
// NearFuture
//
// Created by neon443 on 13/06/2025.
//
import Foundation
import SwiftUI
struct hapticHeavy<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)
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 swift(>=6.2)
content.buttonStyle(.glass)
#else
content.buttonStyle(.borderedProminent)
.clipShape(RoundedRectangle(cornerRadius: 15))
.tint(.two)
#endif
}
}
struct navigationInlineLarge: ViewModifier {
func body(content: Content) -> some View {
#if os(macOS)
content
.toolbarTitleDisplayMode(.inlineLarge)
#else
content
.navigationBarTitleDisplayMode(.inline)
#endif
}
}
struct presentationSizeForm: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 18, macOS 15, *) {
content.presentationSizing(.form)
} else {
content
}
}
}