diff --git a/Config.xcconfig b/Config.xcconfig index 7118d9a..b3278ca 100644 --- a/Config.xcconfig +++ b/Config.xcconfig @@ -12,6 +12,6 @@ TEAM_ID = 8JGND254B7 BUNDLE_ID = com.neon443.NearFuture BUNDLE_ID_WIDGETS = com.neon443.NearFuture.widgets GROUP_ID = group.NearFuture -VERSION = 4.4.0 +VERSION = 5 NAME = Near Future BUILD_NUMBER = 1 diff --git a/MacNearFuture/AboutView.swift b/MacNearFuture/AboutView.swift new file mode 100644 index 0000000..1b3fc9f --- /dev/null +++ b/MacNearFuture/AboutView.swift @@ -0,0 +1,40 @@ +// +// AboutView.swift +// MacNearFuture +// +// Created by neon443 on 28/05/2025. +// + +import SwiftUI + +struct AboutView: View { + var body: some View { + VStack(alignment: .center) { + Image(nsImage: #imageLiteral(resourceName: "NearFutureIcon.png")) + .resizable() + .scaledToFit() + .frame(width: 100) + .clipShape(RoundedRectangle(cornerRadius: 25)) + Text("Near Future") + .bold() + .monospaced() + .font(.title) + Text("Version " + getVersion() + " (\(getBuildID()))") + .padding(.bottom) + Text("© 2024-2025 neon443, Inc") + .padding(.bottom) + Link("Developer Website", destination: URL(string: "https://neon443.xyz")!) + } + .padding() + .padding() + .containerBackground(.ultraThinMaterial, for: .window) + .toolbar(removing: .title) + .toolbarBackground(.hidden, for: .windowToolbar) + .windowMinimizeBehavior(.disabled) + .windowFullScreenBehavior(.disabled) + } +} + +#Preview { + AboutView() +} diff --git a/MacNearFuture/MacNearFuture.entitlements b/MacNearFuture/MacNearFuture.entitlements new file mode 100644 index 0000000..69112c6 --- /dev/null +++ b/MacNearFuture/MacNearFuture.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.developer.icloud-container-identifiers + + com.apple.developer.ubiquity-kvstore-identifier + $(TeamIdentifierPrefix)$(CFBundleIdentifier) + com.apple.security.app-sandbox + + com.apple.security.application-groups + + group.NearFuture + + com.apple.security.files.user-selected.read-only + + + diff --git a/MacNearFuture/MacNearFutureApp.swift b/MacNearFuture/MacNearFutureApp.swift new file mode 100644 index 0000000..434bfa1 --- /dev/null +++ b/MacNearFuture/MacNearFutureApp.swift @@ -0,0 +1,72 @@ +// +// MacNearFutureApp.swift +// MacNearFuture +// +// Created by neon443 on 21/05/2025. +// + +import Foundation +import SwiftUI + +@main +struct NearFutureApp: App { + @Environment(\.openWindow) var openWindow + @StateObject var viewModel: EventViewModel = EventViewModel() + @StateObject var settingsModel: SettingsViewModel = SettingsViewModel() + + var body: some Scene { + WindowGroup { + ContentView( + viewModel: viewModel, + settingsModel: settingsModel + ) + } + .defaultSize(width: 550, height: 650) + .commands { + CommandGroup(replacing: CommandGroupPlacement.appInfo) { + Button("About Near Future") { + openWindow(id: "about") + } + } + NearFutureCommands() + } + + WindowGroup("Edit Event", for: Event.ID.self) { $eventID in + if viewModel.events.first(where: {$0.id == eventID}) == nil { + AddEventView( + viewModel: viewModel + ) + } else { + EditEventView( + viewModel: viewModel, + event: Binding( + get: { + viewModel.events.first(where: {$0.id == eventID}) ?? viewModel.template + }, + set: { newValue in + viewModel.editEvent(newValue) + } + ) + ) + } + } + .defaultSize(width: 480, height: 550) + .windowIdealSize(.fitToContent) + .restorationBehavior(.disabled) + + Window("About Near Future", id: "about") { + AboutView() + } + .windowBackgroundDragBehavior(.enabled) + .windowResizability(.contentSize) + .restorationBehavior(.disabled) + .defaultPosition(UnitPoint.center) + + Settings { + SettingsView( + viewModel: viewModel, + settingsModel: settingsModel + ) + } + } +} diff --git a/MacNearFuture/NFCommands.swift b/MacNearFuture/NFCommands.swift new file mode 100644 index 0000000..29c6083 --- /dev/null +++ b/MacNearFuture/NFCommands.swift @@ -0,0 +1,17 @@ +// +// NFCommands.swift +// MacNearFuture +// +// Created by neon443 on 21/05/2025. +// + +import Foundation +import SwiftUI + +struct NearFutureCommands: Commands { + var body: some Commands { + CommandGroup(after: CommandGroupPlacement.appInfo) { +// Text("hi") + } + } +} diff --git a/MacNearFuture/Views/ArchiveView.swift b/MacNearFuture/Views/ArchiveView.swift new file mode 100644 index 0000000..ac0af6c --- /dev/null +++ b/MacNearFuture/Views/ArchiveView.swift @@ -0,0 +1,33 @@ +// +// ArchiveView.swift +// MacNearFuture +// +// Created by neon443 on 28/05/2025. +// + +import SwiftUI + +struct ArchiveView: View { + @StateObject var viewModel: EventViewModel + @StateObject var settingsModel: SettingsViewModel + + var filteredEvents: [Event] { + return viewModel.events.filter { $0.complete } + } + + var body: some View { + ScrollView { + ForEach(filteredEvents) { event in + EventListView(viewModel: viewModel, event: event) + } + } + .scrollContentBackground(.hidden) + } +} + +#Preview { + ArchiveView( + viewModel: dummyEventViewModel(), + settingsModel: dummySettingsViewModel() + ) +} diff --git a/MacNearFuture/Views/ContentViewMac.swift b/MacNearFuture/Views/ContentViewMac.swift new file mode 100644 index 0000000..f4e9684 --- /dev/null +++ b/MacNearFuture/Views/ContentViewMac.swift @@ -0,0 +1,88 @@ +// +// ContentView.swift +// MacNearFuture +// +// Created by neon443 on 21/05/2025. +// + +import SwiftUI + +struct ContentView: View { + @StateObject var viewModel: EventViewModel + @StateObject var settingsModel: SettingsViewModel + + @State private var showAddEventView: Bool = false + @State private var symbolSearchInput: String = "" + + var body: some View { + NavigationSplitView { + List { + NavigationLink { + HomeView( + viewModel: viewModel, + settingsModel: settingsModel + ) + } label: { + Image(systemName: "house") + Text("Home") + } + NavigationLink { + ArchiveView( + viewModel: viewModel, + settingsModel: settingsModel + ) + } label: { + Image(systemName: "tray.full") + 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: { + Text("Welcome to Near Future") + } + .tint(settingsModel.settings.tint.color) + .frame(minWidth: 450, minHeight: 550) + .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") + } + } + } +} + +#Preview { + ContentView( + viewModel: dummyEventViewModel(), + settingsModel: dummySettingsViewModel() + ) +} diff --git a/MacNearFuture/Views/HomeView.swift b/MacNearFuture/Views/HomeView.swift new file mode 100644 index 0000000..f21b7c5 --- /dev/null +++ b/MacNearFuture/Views/HomeView.swift @@ -0,0 +1,50 @@ +// +// HomeView.swift +// MacNearFuture +// +// Created by neon443 on 28/05/2025. +// + +import SwiftUI + +struct HomeView: View { + @StateObject var viewModel: EventViewModel + @StateObject var settingsModel: SettingsViewModel + + @State private var searchInput: String = "" + + var filteredEvents: [Event] { + if searchInput.isEmpty { + if settingsModel.settings.showCompletedInHome { + return viewModel.events + } else { + return viewModel.events.filter() { !$0.complete } + } + } else { + return viewModel.events.filter { + $0.name.localizedCaseInsensitiveContains(searchInput) || + $0.notes.localizedCaseInsensitiveContains(searchInput) + } + } + } + + var body: some View { + ScrollView { + ForEach(viewModel.events) { event in + if filteredEvents.contains(event) { + EventListView(viewModel: viewModel, event: event) + .id(event) + } + } + } + .searchable(text: $searchInput) + .scrollContentBackground(.hidden) + } +} + +#Preview { + HomeView( + viewModel: dummyEventViewModel(), + settingsModel: dummySettingsViewModel() + ) +} diff --git a/MacNearFuture/Views/SettingsView.swift b/MacNearFuture/Views/SettingsView.swift new file mode 100644 index 0000000..34ad279 --- /dev/null +++ b/MacNearFuture/Views/SettingsView.swift @@ -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() + ) +} diff --git a/NearFuture.xcodeproj/project.pbxproj b/NearFuture.xcodeproj/project.pbxproj index 402ded8..0a3c59a 100644 --- a/NearFuture.xcodeproj/project.pbxproj +++ b/NearFuture.xcodeproj/project.pbxproj @@ -7,14 +7,44 @@ objects = { /* Begin PBXBuildFile section */ + A90D49382DDE0FAF00781124 /* ContentViewMac.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49332DDE0FAF00781124 /* ContentViewMac.swift */; }; + A90D493D2DDE10B200781124 /* NearFutureIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = A949F8312DCAAA8A0064DCA0 /* NearFutureIcon.png */; }; + A90D493E2DDE10CF00781124 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C28D2D24011A00E4F9B1 /* Assets.xcassets */; }; + A90D49422DDE114100781124 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Events.swift */; }; + A90D49442DDE1C7600781124 /* 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 */; }; + A90D49522DDE2D0000781124 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49512DDE2D0000781124 /* Extensions.swift */; }; + A90D49532DDE2D0000781124 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49512DDE2D0000781124 /* Extensions.swift */; }; + A90D495B2DDE2EDB00781124 /* MacNearFutureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495A2DDE2EDB00781124 /* MacNearFutureApp.swift */; }; + A90D495E2DDE3C7400781124 /* NFCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495D2DDE3C7400781124 /* NFCommands.swift */; }; + A90D495F2DDE3C7400781124 /* NFCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D495D2DDE3C7400781124 /* NFCommands.swift */; }; + A90D49612DDE626300781124 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49602DDE626300781124 /* Settings.swift */; }; + A90D49622DDE626300781124 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49602DDE626300781124 /* Settings.swift */; }; + A90D49632DDE626300781124 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90D49602DDE626300781124 /* Settings.swift */; }; A914FA4B2DD26C6800856265 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4A2DD26C0F00856265 /* HomeView.swift */; }; A914FA4D2DD2768900856265 /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A914FA4C2DD2768900856265 /* WhatsNewView.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 */; }; - A920C28C2D24011400E4F9B1 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Item.swift */; }; + A920C28C2D24011400E4F9B1 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Events.swift */; }; A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A920C28D2D24011A00E4F9B1 /* 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 */; }; A949F84B2DCAABE00064DCA0 /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83A2DCAABE00064DCA0 /* ArchiveView.swift */; }; A949F84C2DCAABE00064DCA0 /* AddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F83C2DCAABE00064DCA0 /* AddEventView.swift */; }; @@ -27,16 +57,25 @@ A949F8532DCAABE00064DCA0 /* ImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8452DCAABE00064DCA0 /* ImportView.swift */; }; A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8462DCAABE00064DCA0 /* SettingsView.swift */; }; A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8482DCAABE00064DCA0 /* StatsView.swift */; }; - A949F8562DCAABE00064DCA0 /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8432DCAABE00064DCA0 /* ExportView.swift */; }; - A949F8592DCAAD670064DCA0 /* NearFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8572DCAAD670064DCA0 /* NearFutureTests.swift */; }; A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F85D2DCABB420064DCA0 /* Buttons.swift */; }; - A979F6052D270AF00094C0B3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6042D270AF00094C0B3 /* WidgetKit.framework */; }; - A979F6072D270AF00094C0B3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A979F6062D270AF00094C0B3 /* SwiftUI.framework */; }; + 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 */; }; A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */; }; A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A979F60F2D270AF80094C0B3 /* Assets.xcassets */; }; A979F6142D270AF90094C0B3 /* NearFutureWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - A979F6182D2714310094C0B3 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Item.swift */; }; + A979F6182D2714310094C0B3 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = A920C28B2D24011400E4F9B1 /* Events.swift */; }; + A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CD2DE7308E0008D61C /* ArchiveView.swift */; }; + A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20CF2DE731BD0008D61C /* HomeView.swift */; }; + A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98C20D32DE7339E0008D61C /* AboutView.swift */; }; + A9BAC6882DFF242300EC8E44 /* CompleteEventButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */; }; + A9BAC6892DFF242300EC8E44 /* CompleteEventButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */; }; + A9D1C34D2DFE10FA00703C2D /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949F8402DCAABE00064DCA0 /* EventListView.swift */; }; A9FC7EEA2D2823920020D75B /* NearFutureWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */; }; /* End PBXBuildFile section */ @@ -48,12 +87,12 @@ remoteGlobalIDString = A979F6012D270AF00094C0B3; remoteInfo = NearFutureWidgetsExtension; }; - A980FC3B2D93FB2B006A778F /* PBXContainerItemProxy */ = { + A98C20D12DE732B10008D61C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = A920C27C2D24011300E4F9B1 /* Project object */; proxyType = 1; - remoteGlobalIDString = A920C2832D24011300E4F9B1; - remoteInfo = NearFuture; + remoteGlobalIDString = A979F6012D270AF00094C0B3; + remoteInfo = NearFutureWidgetsExtension; }; /* End PBXContainerItemProxy section */ @@ -72,15 +111,29 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + A90D491F2DDE08E400781124 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + A90D49262DDE0FA400781124 /* Near Future.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Near Future.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + A90D49332DDE0FAF00781124 /* ContentViewMac.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentViewMac.swift; sourceTree = ""; }; + A90D49342DDE0FAF00781124 /* MacNearFuture.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MacNearFuture.entitlements; sourceTree = ""; }; + A90D49432DDE1C1100781124 /* Tints.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Tints.xcassets; sourceTree = ""; }; + A90D49512DDE2D0000781124 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; + A90D495A2DDE2EDB00781124 /* MacNearFutureApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MacNearFutureApp.swift; path = MacNearFuture/MacNearFutureApp.swift; sourceTree = SOURCE_ROOT; }; + A90D495D2DDE3C7400781124 /* NFCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFCommands.swift; sourceTree = ""; }; + A90D49602DDE626300781124 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; + A90D49652DDE658100781124 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A90FDE222DC0D4310012790C /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; A914FA4A2DD26C0F00856265 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; 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; }; + A91EF8062DFC8B8B00B8463D /* ColorCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorCodable.swift; sourceTree = ""; }; + A91EF80A2DFC910000B8463D /* ViewModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifiers.swift; sourceTree = ""; }; + A91EF80F2DFCB66C00B8463D /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolsLoader.swift; sourceTree = ""; }; + A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolsPicker.swift; sourceTree = ""; }; 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 = ""; }; - A920C28B2D24011400E4F9B1 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = ""; }; + A920C28B2D24011400E4F9B1 /* Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = ""; }; A920C28D2D24011A00E4F9B1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - A920C28F2D24011A00E4F9B1 /* NearFuture.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NearFuture.entitlements; sourceTree = ""; }; A920C2912D24011A00E4F9B1 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; A949F82E2DCAAA640064DCA0 /* NearFutureIcon.pxd */ = {isa = PBXFileReference; lastKnownFileType = file; path = NearFutureIcon.pxd; sourceTree = ""; }; A949F82F2DCAAA640064DCA0 /* NearFutureIconDark.pxd */ = {isa = PBXFileReference; lastKnownFileType = file; path = NearFutureIconDark.pxd; sourceTree = ""; }; @@ -97,46 +150,35 @@ A949F8452DCAABE00064DCA0 /* ImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportView.swift; sourceTree = ""; }; A949F8462DCAABE00064DCA0 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; A949F8482DCAABE00064DCA0 /* StatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsView.swift; sourceTree = ""; }; - A949F8572DCAAD670064DCA0 /* NearFutureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureTests.swift; sourceTree = ""; }; A949F85D2DCABB420064DCA0 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = ""; }; - A979F58B2D2700680094C0B3 /* NearFutureWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsBundle.swift; sourceTree = ""; }; - A979F58D2D2700680094C0B3 /* NearFutureWidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsLiveActivity.swift; sourceTree = ""; }; - A979F58F2D2700680094C0B3 /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = ""; }; - A979F5912D2700680094C0B3 /* AppIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntent.swift; sourceTree = ""; }; - A979F5932D27006D0094C0B3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - A979F5952D27006D0094C0B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A95E9ED72DFC742B00ED655F /* AccentIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccentIcon.swift; sourceTree = ""; }; A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NearFutureWidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; - A979F6042D270AF00094C0B3 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; - A979F6062D270AF00094C0B3 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsBundle.swift; sourceTree = ""; }; A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgetsLiveActivity.swift; sourceTree = ""; }; A979F60F2D270AF80094C0B3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - A979F6112D270AF90094C0B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A980FC302D920097006A778F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A980FC372D93FB2B006A778F /* NearFutureTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NearFutureTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - A9C05E412D2805D7007DC497 /* NearFutureWidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NearFutureWidgetsExtension.entitlements; sourceTree = ""; }; + A98C20CD2DE7308E0008D61C /* ArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveView.swift; sourceTree = ""; }; + A98C20CF2DE731BD0008D61C /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; + A98C20D32DE7339E0008D61C /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; + A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompleteEventButton.swift; sourceTree = ""; }; A9FC7EE92D28238A0020D75B /* NearFutureWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearFutureWidgets.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + A90D49232DDE0FA400781124 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; A920C2812D24011300E4F9B1 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A920C2BE2D24021A00E4F9B1 /* SFSymbolsPicker in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; A979F5FF2D270AF00094C0B3 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - A979F6072D270AF00094C0B3 /* SwiftUI.framework in Frameworks */, - A979F6052D270AF00094C0B3 /* WidgetKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - A980FC342D93FB2B006A778F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -146,15 +188,80 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + A90D49202DDE0A3B00781124 /* Model */ = { + isa = PBXGroup; + children = ( + A920C28B2D24011400E4F9B1 /* Events.swift */, + A90D49602DDE626300781124 /* Settings.swift */, + A91EF8062DFC8B8B00B8463D /* ColorCodable.swift */, + A95E9ED72DFC742B00ED655F /* AccentIcon.swift */, + A91EF8162DFD77A500B8463D /* SymbolsPicker */, + ); + path = Model; + sourceTree = ""; + }; + A90D49362DDE0FAF00781124 /* MacNearFuture */ = { + isa = PBXGroup; + children = ( + A90D495A2DDE2EDB00781124 /* MacNearFutureApp.swift */, + A98C20D32DE7339E0008D61C /* AboutView.swift */, + A90D495D2DDE3C7400781124 /* NFCommands.swift */, + A90D493F2DDE10EC00781124 /* Views */, + A90D49342DDE0FAF00781124 /* MacNearFuture.entitlements */, + ); + path = MacNearFuture; + sourceTree = ""; + }; + A90D493F2DDE10EC00781124 /* Views */ = { + isa = PBXGroup; + children = ( + A90D49332DDE0FAF00781124 /* ContentViewMac.swift */, + A98C20CF2DE731BD0008D61C /* HomeView.swift */, + A98C20CD2DE7308E0008D61C /* ArchiveView.swift */, + A91EF80F2DFCB66C00B8463D /* SettingsView.swift */, + ); + path = Views; + sourceTree = ""; + }; + A90D49412DDE112700781124 /* Shared */ = { + isa = PBXGroup; + children = ( + A920C2872D24011400E4F9B1 /* NearFutureApp.swift */, + A90D49512DDE2D0000781124 /* Extensions.swift */, + A90D49202DDE0A3B00781124 /* Model */, + A91EF80A2DFC910000B8463D /* ViewModifiers.swift */, + A9BAC6872DFF238100EC8E44 /* CompleteEventButton.swift */, + ); + path = Shared; + sourceTree = ""; + }; + A90D49542DDE2D5800781124 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + A91EF8162DFD77A500B8463D /* SymbolsPicker */ = { + isa = PBXGroup; + children = ( + A91EF8172DFD77BF00B8463D /* SymbolsLoader.swift */, + A91EF81B2DFD796600B8463D /* SymbolsPicker.swift */, + ); + path = SymbolsPicker; + sourceTree = ""; + }; A920C27B2D24011300E4F9B1 = { isa = PBXGroup; children = ( + A90D491F2DDE08E400781124 /* README.md */, A90FDE222DC0D4310012790C /* Config.xcconfig */, - A949F8002DCAA0340064DCA0 /* Resources */, + A90D49412DDE112700781124 /* Shared */, A920C2862D24011400E4F9B1 /* NearFuture */, + A90D49362DDE0FAF00781124 /* MacNearFuture */, + A949F8002DCAA0340064DCA0 /* Resources */, A979F6082D270AF00094C0B3 /* NearFutureWidgets */, - A949F8582DCAAD670064DCA0 /* NearFutureTests */, - A979F6032D270AF00094C0B3 /* Frameworks */, + A90D49542DDE2D5800781124 /* Frameworks */, A920C2852D24011400E4F9B1 /* Products */, ); sourceTree = ""; @@ -164,7 +271,7 @@ children = ( A920C2842D24011400E4F9B1 /* NearFuture.app */, A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */, - A980FC372D93FB2B006A778F /* NearFutureTests.xctest */, + A90D49262DDE0FA400781124 /* Near Future.app */, ); name = Products; sourceTree = ""; @@ -172,11 +279,7 @@ A920C2862D24011400E4F9B1 /* NearFuture */ = { isa = PBXGroup; children = ( - A920C2872D24011400E4F9B1 /* NearFutureApp.swift */, - A920C28B2D24011400E4F9B1 /* Item.swift */, A949F84A2DCAABE00064DCA0 /* Views */, - A980FC302D920097006A778F /* Info.plist */, - A920C28F2D24011A00E4F9B1 /* NearFuture.entitlements */, A920C2902D24011A00E4F9B1 /* Preview Content */, ); path = NearFuture; @@ -185,7 +288,6 @@ A920C2902D24011A00E4F9B1 /* Preview Content */ = { isa = PBXGroup; children = ( - A979F58A2D2700680094C0B3 /* NearFutureWidgets */, A920C2912D24011A00E4F9B1 /* Preview Assets.xcassets */, ); path = "Preview Content"; @@ -196,6 +298,7 @@ children = ( A949F8312DCAAA8A0064DCA0 /* NearFutureIcon.png */, A920C28D2D24011A00E4F9B1 /* Assets.xcassets */, + A90D49432DDE1C1100781124 /* Tints.xcassets */, A949F82E2DCAAA640064DCA0 /* NearFutureIcon.pxd */, A949F82F2DCAAA640064DCA0 /* NearFutureIconDark.pxd */, A949F8302DCAAA640064DCA0 /* NearFutureIconTint.pxd */, @@ -223,10 +326,9 @@ A949F8422DCAABE00064DCA0 /* Home */ = { isa = PBXGroup; children = ( - A949F83F2DCAABE00064DCA0 /* ContentView.swift */, + A914FA4A2DD26C0F00856265 /* HomeView.swift */, A949F8402DCAABE00064DCA0 /* EventListView.swift */, A949F8412DCAABE00064DCA0 /* HelpView.swift */, - A914FA4A2DD26C0F00856265 /* HomeView.swift */, ); path = Home; sourceTree = ""; @@ -254,6 +356,7 @@ A949F84A2DCAABE00064DCA0 /* Views */ = { isa = PBXGroup; children = ( + A949F83F2DCAABE00064DCA0 /* ContentView.swift */, A949F8422DCAABE00064DCA0 /* Home */, A949F83E2DCAABE00064DCA0 /* Events */, A949F83B2DCAABE00064DCA0 /* Archive */, @@ -264,14 +367,6 @@ path = Views; sourceTree = ""; }; - A949F8582DCAAD670064DCA0 /* NearFutureTests */ = { - isa = PBXGroup; - children = ( - A949F8572DCAAD670064DCA0 /* NearFutureTests.swift */, - ); - path = NearFutureTests; - sourceTree = ""; - }; A949F85E2DCABB420064DCA0 /* Misc */ = { isa = PBXGroup; children = ( @@ -281,28 +376,6 @@ path = Misc; sourceTree = ""; }; - A979F58A2D2700680094C0B3 /* NearFutureWidgets */ = { - isa = PBXGroup; - children = ( - A979F58B2D2700680094C0B3 /* NearFutureWidgetsBundle.swift */, - A979F58D2D2700680094C0B3 /* NearFutureWidgetsLiveActivity.swift */, - A979F58F2D2700680094C0B3 /* NearFutureWidgets.swift */, - A979F5912D2700680094C0B3 /* AppIntent.swift */, - A979F5932D27006D0094C0B3 /* Assets.xcassets */, - A979F5952D27006D0094C0B3 /* Info.plist */, - ); - path = NearFutureWidgets; - sourceTree = ""; - }; - A979F6032D270AF00094C0B3 /* Frameworks */ = { - isa = PBXGroup; - children = ( - A979F6042D270AF00094C0B3 /* WidgetKit.framework */, - A979F6062D270AF00094C0B3 /* SwiftUI.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; A979F6082D270AF00094C0B3 /* NearFutureWidgets */ = { isa = PBXGroup; children = ( @@ -310,8 +383,7 @@ A979F6092D270AF00094C0B3 /* NearFutureWidgetsBundle.swift */, A979F60B2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift */, A979F60F2D270AF80094C0B3 /* Assets.xcassets */, - A979F6112D270AF90094C0B3 /* Info.plist */, - A9C05E412D2805D7007DC497 /* NearFutureWidgetsExtension.entitlements */, + A90D49652DDE658100781124 /* Info.plist */, ); path = NearFutureWidgets; sourceTree = ""; @@ -319,6 +391,26 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + A90D49252DDE0FA400781124 /* MacNearFuture */ = { + isa = PBXNativeTarget; + buildConfigurationList = A90D492F2DDE0FA600781124 /* Build configuration list for PBXNativeTarget "MacNearFuture" */; + buildPhases = ( + A90D49222DDE0FA400781124 /* Sources */, + A90D49232DDE0FA400781124 /* Frameworks */, + A90D49242DDE0FA400781124 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + A98C20D22DE732B10008D61C /* PBXTargetDependency */, + ); + name = MacNearFuture; + packageProductDependencies = ( + ); + productName = MacNearFuture; + productReference = A90D49262DDE0FA400781124 /* Near Future.app */; + productType = "com.apple.product-type.application"; + }; A920C2832D24011300E4F9B1 /* NearFuture */ = { isa = PBXNativeTarget; buildConfigurationList = A920C2AB2D24011B00E4F9B1 /* Build configuration list for PBXNativeTarget "NearFuture" */; @@ -335,7 +427,6 @@ ); name = NearFuture; packageProductDependencies = ( - A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */, ); productName = NearFuture; productReference = A920C2842D24011400E4F9B1 /* NearFuture.app */; @@ -358,26 +449,6 @@ productReference = A979F6022D270AF00094C0B3 /* NearFutureWidgetsExtension.appex */; productType = "com.apple.product-type.app-extension"; }; - A980FC362D93FB2B006A778F /* NearFutureTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = A980FC3D2D93FB2B006A778F /* Build configuration list for PBXNativeTarget "NearFutureTests" */; - buildPhases = ( - A980FC332D93FB2B006A778F /* Sources */, - A980FC342D93FB2B006A778F /* Frameworks */, - A980FC352D93FB2B006A778F /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - A980FC3C2D93FB2B006A778F /* PBXTargetDependency */, - ); - name = NearFutureTests; - packageProductDependencies = ( - ); - productName = NearFutureTests; - productReference = A980FC372D93FB2B006A778F /* NearFutureTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -385,19 +456,18 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1620; + LastSwiftUpdateCheck = 1640; LastUpgradeCheck = 1630; TargetAttributes = { + A90D49252DDE0FA400781124 = { + CreatedOnToolsVersion = 16.4; + }; A920C2832D24011300E4F9B1 = { CreatedOnToolsVersion = 15.4; }; A979F6012D270AF00094C0B3 = { CreatedOnToolsVersion = 15.4; }; - A980FC362D93FB2B006A778F = { - CreatedOnToolsVersion = 16.2; - TestTargetID = A920C2832D24011300E4F9B1; - }; }; }; buildConfigurationList = A920C27F2D24011300E4F9B1 /* Build configuration list for PBXProject "NearFuture" */; @@ -410,24 +480,34 @@ ); mainGroup = A920C27B2D24011300E4F9B1; packageReferences = ( - A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */, ); productRefGroup = A920C2852D24011400E4F9B1 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( A920C2832D24011300E4F9B1 /* NearFuture */, + A90D49252DDE0FA400781124 /* MacNearFuture */, A979F6012D270AF00094C0B3 /* NearFutureWidgetsExtension */, - A980FC362D93FB2B006A778F /* NearFutureTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + A90D49242DDE0FA400781124 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A90D49452DDE1C7600781124 /* Tints.xcassets in Resources */, + A90D493E2DDE10CF00781124 /* Assets.xcassets in Resources */, + A90D493D2DDE10B200781124 /* NearFutureIcon.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A920C2822D24011300E4F9B1 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + A90D49442DDE1C7600781124 /* Tints.xcassets in Resources */, A920C2922D24011A00E4F9B1 /* Preview Assets.xcassets in Resources */, A949F8322DCAAA8A0064DCA0 /* NearFutureIcon.png in Resources */, A920C28E2D24011A00E4F9B1 /* Assets.xcassets in Resources */, @@ -438,40 +518,74 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + A90D49462DDE1C7A00781124 /* Tints.xcassets in Resources */, A979F6102D270AF90094C0B3 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - A980FC352D93FB2B006A778F /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + A90D49222DDE0FA400781124 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A91EF80E2DFC9A0C00B8463D /* WhatsNewView.swift in Sources */, + A91EF8192DFD77BF00B8463D /* SymbolsLoader.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 */, + A95E9EE52DFC77E200ED655F /* ExportView.swift in Sources */, + A91EF8132DFCC87D00B8463D /* EditEventView.swift in Sources */, + A91EF8142DFCC87D00B8463D /* AddEventView.swift in Sources */, + A9D1C34D2DFE10FA00703C2D /* EventListView.swift in Sources */, + A98C20D42DE7339E0008D61C /* AboutView.swift in Sources */, + A98C20CE2DE7308E0008D61C /* ArchiveView.swift in Sources */, + A98C20D02DE731BD0008D61C /* HomeView.swift in Sources */, + A90D495B2DDE2EDB00781124 /* MacNearFutureApp.swift in Sources */, + A91EF8082DFC8B8B00B8463D /* ColorCodable.swift in Sources */, + A90D49522DDE2D0000781124 /* Extensions.swift in Sources */, + A91EF81D2DFD796600B8463D /* SymbolsPicker.swift in Sources */, + A90D49422DDE114100781124 /* Events.swift in Sources */, + A90D49382DDE0FAF00781124 /* ContentViewMac.swift in Sources */, + A90D49622DDE626300781124 /* Settings.swift in Sources */, + A95E9ED32DFC703200ED655F /* iCloudSettingsView.swift in Sources */, + A96609E72DFD800000DBFA78 /* HelpView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A920C2802D24011300E4F9B1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A920C28C2D24011400E4F9B1 /* Item.swift in Sources */, + A920C28C2D24011400E4F9B1 /* Events.swift in Sources */, A949F84B2DCAABE00064DCA0 /* ArchiveView.swift in Sources */, A914FA4F2DD276D200856265 /* AboutView.swift in Sources */, A949F84C2DCAABE00064DCA0 /* AddEventView.swift in Sources */, A914FA4B2DD26C6800856265 /* HomeView.swift in Sources */, A949F84D2DCAABE00064DCA0 /* EditEventView.swift in Sources */, A949F84E2DCAABE00064DCA0 /* ContentView.swift in Sources */, + A90D49612DDE626300781124 /* Settings.swift in Sources */, + A90D495F2DDE3C7400781124 /* NFCommands.swift in Sources */, A949F84F2DCAABE00064DCA0 /* EventListView.swift in Sources */, + A91EF8092DFC8B8B00B8463D /* ColorCodable.swift in Sources */, A949F8502DCAABE00064DCA0 /* HelpView.swift in Sources */, A949F85F2DCABB420064DCA0 /* Buttons.swift in Sources */, A914FA4D2DD2768900856265 /* WhatsNewView.swift in Sources */, + A91EF80B2DFC910000B8463D /* ViewModifiers.swift in Sources */, A949F8512DCAABE00064DCA0 /* ExportView.swift in Sources */, + A95E9ED82DFC742B00ED655F /* AccentIcon.swift in Sources */, + A90D49532DDE2D0000781124 /* Extensions.swift in Sources */, A949F8522DCAABE00064DCA0 /* iCloudSettingsView.swift in Sources */, + A9BAC6882DFF242300EC8E44 /* CompleteEventButton.swift in Sources */, A949F8532DCAABE00064DCA0 /* ImportView.swift in Sources */, A949F8542DCAABE00064DCA0 /* SettingsView.swift in Sources */, + A91EF81E2DFD796600B8463D /* SymbolsPicker.swift in Sources */, A949F8552DCAABE00064DCA0 /* StatsView.swift in Sources */, + A91EF81A2DFD77BF00B8463D /* SymbolsLoader.swift in Sources */, A920C2882D24011400E4F9B1 /* NearFutureApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -480,19 +594,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A979F6182D2714310094C0B3 /* Item.swift in Sources */, + A979F6182D2714310094C0B3 /* Events.swift in Sources */, + A91EF81C2DFD796600B8463D /* SymbolsPicker.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 */, A979F60C2D270AF00094C0B3 /* NearFutureWidgetsLiveActivity.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - A980FC332D93FB2B006A778F /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - A949F8592DCAAD670064DCA0 /* NearFutureTests.swift in Sources */, - A949F8562DCAABE00064DCA0 /* ExportView.swift in Sources */, + A90D49632DDE626300781124 /* Settings.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -505,14 +616,76 @@ target = A979F6012D270AF00094C0B3 /* NearFutureWidgetsExtension */; targetProxy = A979F6122D270AF90094C0B3 /* PBXContainerItemProxy */; }; - A980FC3C2D93FB2B006A778F /* PBXTargetDependency */ = { + A98C20D22DE732B10008D61C /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = A920C2832D24011300E4F9B1 /* NearFuture */; - targetProxy = A980FC3B2D93FB2B006A778F /* PBXContainerItemProxy */; + target = A979F6012D270AF00094C0B3 /* NearFutureWidgetsExtension */; + targetProxy = A98C20D12DE732B10008D61C /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + A90D49302DDE0FA600781124 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "bloo blue green pink purple red yellow"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = MacNearFuture/MacNearFuture.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 8JGND254B7; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 15; + MARKETING_VERSION = "$(VERSION)"; + PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture; + PRODUCT_NAME = "Near Future"; + REGISTER_APP_GROUPS = YES; + SDKROOT = macosx; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + A90D49312DDE0FA600781124 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "bloo blue green pink purple red yellow"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = MacNearFuture/MacNearFuture.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 8JGND254B7; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 15; + MARKETING_VERSION = "$(VERSION)"; + PRODUCT_BUNDLE_IDENTIFIER = com.neon443.NearFuture; + PRODUCT_NAME = "Near Future"; + REGISTER_APP_GROUPS = YES; + SDKROOT = macosx; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; A920C2A92D24011B00E4F9B1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -660,8 +833,7 @@ ENABLE_HARDENED_RUNTIME = NO; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = NearFuture/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "$(NAME)"; + INFOPLIST_KEY_CFBundleDisplayName = "Near Future"; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; @@ -687,8 +859,8 @@ REGISTER_APP_GROUPS = YES; SDKROOT = auto; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = YES; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -713,8 +885,7 @@ ENABLE_HARDENED_RUNTIME = NO; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = NearFuture/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "$(NAME)"; + INFOPLIST_KEY_CFBundleDisplayName = "Near Future"; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; @@ -737,8 +908,8 @@ REGISTER_APP_GROUPS = YES; SDKROOT = auto; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = YES; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -758,7 +929,7 @@ CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)"; DEVELOPMENT_TEAM = "$(TEAM_ID)"; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = NearFutureWidgets/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/NearFutureWidgets/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = NearFutureWidgets; INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 17; @@ -778,8 +949,9 @@ REGISTER_APP_GROUPS = YES; SDKROOT = iphoneos; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -799,7 +971,7 @@ CURRENT_PROJECT_VERSION = "$(BUILD_NUMBER)"; DEVELOPMENT_TEAM = "$(TEAM_ID)"; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = NearFutureWidgets/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/NearFutureWidgets/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = NearFutureWidgets; INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 17; @@ -815,8 +987,9 @@ REGISTER_APP_GROUPS = YES; SDKROOT = iphoneos; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -825,61 +998,18 @@ }; name = Release; }; - A980FC3E2D93FB2B006A778F /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16; - MACOSX_DEPLOYMENT_TARGET = 13; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.neon443.NearFutureTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NearFuture.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/NearFuture"; - XROS_DEPLOYMENT_TARGET = 1; - }; - name = Debug; - }; - A980FC3F2D93FB2B006A778F /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = A90FDE222DC0D4310012790C /* Config.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16; - MACOSX_DEPLOYMENT_TARGET = 13; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.neon443.NearFutureTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NearFuture.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/NearFuture"; - XROS_DEPLOYMENT_TARGET = 1; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + A90D492F2DDE0FA600781124 /* Build configuration list for PBXNativeTarget "MacNearFuture" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A90D49302DDE0FA600781124 /* Debug */, + A90D49312DDE0FA600781124 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; A920C27F2D24011300E4F9B1 /* Build configuration list for PBXProject "NearFuture" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -907,35 +1037,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - A980FC3D2D93FB2B006A778F /* Build configuration list for PBXNativeTarget "NearFutureTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - A980FC3E2D93FB2B006A778F /* Debug */, - A980FC3F2D93FB2B006A778F /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* 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 */ - A920C2BD2D24021A00E4F9B1 /* SFSymbolsPicker */ = { - isa = XCSwiftPackageProductDependency; - package = A920C2BC2D24021900E4F9B1 /* XCRemoteSwiftPackageReference "SFSymbolsPicker" */; - productName = SFSymbolsPicker; - }; -/* End XCSwiftPackageProductDependency section */ }; rootObject = A920C27C2D24011300E4F9B1 /* Project object */; } diff --git a/NearFuture.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/NearFuture.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index b5805b7..0000000 --- a/NearFuture.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -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 -} diff --git a/NearFuture.xcodeproj/xcshareddata/xcschemes/MacNearFuture.xcscheme b/NearFuture.xcodeproj/xcshareddata/xcschemes/MacNearFuture.xcscheme new file mode 100644 index 0000000..d4dc62b --- /dev/null +++ b/NearFuture.xcodeproj/xcshareddata/xcschemes/MacNearFuture.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NearFuture.xcodeproj/xcuserdata/neon443.xcuserdatad/xcschemes/xcschememanagement.plist b/NearFuture.xcodeproj/xcuserdata/neon443.xcuserdatad/xcschemes/xcschememanagement.plist index 3d4f3de..87d566a 100644 --- a/NearFuture.xcodeproj/xcuserdata/neon443.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/NearFuture.xcodeproj/xcuserdata/neon443.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,19 +4,29 @@ SchemeUserState - NearFuture.xcscheme_^#shared#^_ + MacNearFuture.xcscheme_^#shared#^_ orderHint 1 - NearFutureWidgetsExtension.xcscheme_^#shared#^_ + NearFuture.xcscheme_^#shared#^_ orderHint 0 + NearFutureWidgetsExtension.xcscheme_^#shared#^_ + + orderHint + 2 + SuppressBuildableAutocreation + A90D49252DDE0FA400781124 + + primary + + A920C2832D24011300E4F9B1 primary diff --git a/NearFuture/Home/EventListView.swift b/NearFuture/Home/EventListView.swift deleted file mode 100644 index c79174d..0000000 --- a/NearFuture/Home/EventListView.swift +++ /dev/null @@ -1,170 +0,0 @@ -// -// EventListView.swift -// NearFuture -// -// Created by neon443 on 18/04/2025. -// - -import SwiftUI -import SwiftData - -struct EventListView: View { - @ObservedObject var viewModel: EventViewModel - @State var event: Event - - var body: some View { - NavigationLink() { - EditEventView( - viewModel: viewModel, - event: $event - ) - } label: { - ZStack { - HStack { - RoundedRectangle(cornerRadius: 5) - .frame(width: 7) - .foregroundStyle( - event.color.color.opacity( - event.complete ? 0.5 : 1 - ) - ) - VStack(alignment: .leading) { - HStack { - Image(systemName: event.symbol) - .resizable() - .scaledToFit() - .frame(width: 20, height: 20) - .shadow(radius: 5) - .foregroundStyle( - .one.opacity( - event.complete ? 0.5 : 1 - ) - ) - Text("\(event.name)") - .font(.headline) - .foregroundStyle(.one) - .strikethrough(event.complete) - .multilineTextAlignment(.leading) - } - if !event.notes.isEmpty { - Text(event.notes) - .font(.subheadline) - .foregroundStyle(.one.opacity(0.8)) - .multilineTextAlignment(.leading) - } - Text( - event.date.formatted( - date: .long, - time: .shortened - ) - ) - .font(.subheadline) - .foregroundStyle( - .one.opacity( - event.complete ? 0.5 : 1 - ) - ) - if event.recurrence != .none { - Text("Occurs \(event.recurrence.rawValue)") - .font(.subheadline) - .foregroundStyle( - .one.opacity(event.complete ? 0.5 : 1)) - } - } - Spacer() - VStack { - Text("\(daysUntilEvent(event.date).long)") - .font(.subheadline) - .foregroundStyle(.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) - } - } - .buttonStyle(.borderless) - .frame(maxWidth: 25, maxHeight: 25) - .shadow(radius: 5) - .padding(.trailing, 5) - } - .padding(.vertical, 5) - .background(.ultraThinMaterial) - .overlay( - RoundedRectangle(cornerRadius: 10) - .stroke( - .one.opacity(appearance == .dark ? 0.5 : 1), - lineWidth: 1 - ) - ) - .clipShape( - RoundedRectangle(cornerRadius: 10) - ) - .fixedSize(horizontal: false, vertical: true) - } - .transition(.opacity) - .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) - } -} diff --git a/NearFuture/Info.plist b/NearFuture/Info.plist deleted file mode 100644 index dfce773..0000000 --- a/NearFuture/Info.plist +++ /dev/null @@ -1,10 +0,0 @@ - - - - - AppIntents - - com.neon443.NearFuture.CompleteEvent - - - diff --git a/NearFuture/Misc/HelpView.swift b/NearFuture/Misc/HelpView.swift deleted file mode 100644 index 24d4909..0000000 --- a/NearFuture/Misc/HelpView.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// ArchiveHelp.swift -// NearFuture -// -// Created by neon443 on 26/04/2025. -// - - -import SwiftUI - -enum HelpType { - case Search - case Archive -} - -struct HelpView: View { - /// initialises a Search HelpView - /// - init(searchInput: Binding, focusedField: Field?) { - _searchInput = searchInput - self.helpType = .Search - _showAddEvent = .constant(false) - } - - /// initialises an Archive HelpView - /// - init(showAddEvent: Binding) { - _showAddEvent = showAddEvent - self.helpType = .Archive - _searchInput = .constant("") - self.focusedField = nil - } - - @Binding var searchInput: String - @FocusState var focusedField: Field? - - @Binding var showAddEvent: Bool - - var helpType: HelpType - var details: ( - symbol: String, - title: String, - body: String, - buttonAction: () -> (), - buttonSymbol: String, - buttonText: String - ) { - switch helpType { - case .Search: - return ( - symbol: "questionmark.app.dashed", - title: "Looking for something?", - body: "Tip: The Search bar searches event names and notes.", - buttonAction: { - searchInput = "" - focusedField = nil - }, - buttonSymbol: "xmark", - buttonText: "Clear Filters" - ) - case .Archive: - return ( - symbol: "eyes", - title: "Nothing to see here...", - body: "The Archive contains events that have been marked as complete.", - buttonAction: { - showAddEvent.toggle() - }, - buttonSymbol: "plus", - buttonText: "Create an event" - ) - } - } - var body: some View { - List { - ZStack { - Color(.tintColor) - .opacity(0.4) - .padding(.horizontal, -15) - .blur(radius: 5) - HStack { - Image(systemName: details.symbol) - .resizable() - .scaledToFit() - .frame(width: 30, height: 30) - .padding(.trailing) - Text(details.title) - .bold() - .font(.title2) - } - } - .listRowSeparator(.hidden) - Text(details.body) - Button() { - details.buttonAction() - } label: { - HStack { - Image(systemName: details.buttonSymbol) - .bold() - Text(details.buttonText) - } - .foregroundStyle(Color.accentColor) - } - } - .scrollContentBackground(.hidden) - } -} - -#Preview { - HelpView(searchInput: .constant(""), focusedField: nil) - HelpView(showAddEvent: .constant(false)) -} diff --git a/NearFuture/NearFuture.entitlements b/NearFuture/NearFuture.entitlements index 057d0cc..82eaccb 100644 --- a/NearFuture/NearFuture.entitlements +++ b/NearFuture/NearFuture.entitlements @@ -2,10 +2,6 @@ - aps-environment - development - com.apple.developer.aps-environment - development com.apple.developer.icloud-container-identifiers com.apple.developer.ubiquity-kvstore-identifier diff --git a/NearFuture/NearFutureApp.swift b/NearFuture/NearFutureApp.swift deleted file mode 100644 index 96ae80f..0000000 --- a/NearFuture/NearFutureApp.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// NearFutureApp.swift -// NearFuture -// -// Created by neon443 on 24/12/2024. -// - -import SwiftUI -import SwiftData - -@main -struct NearFutureApp: App { -// var sharedModelContainer: ModelContainer = { -// let schema = Schema([ -// Item.self, -// ]) -// let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) -// -// do { -// return try ModelContainer(for: schema, configurations: [modelConfiguration]) -// } catch { -// fatalError("Could not create ModelContainer: \(error)") -// } -// }() - @StateObject var settingsModel: SettingsViewModel = SettingsViewModel() - var body: some Scene { - WindowGroup { - ContentView( - viewModel: EventViewModel(), - settingsModel: settingsModel - ) - .tint(settingsModel.settings.tint.color) - } -// .modelContainer(sharedModelContainer) - } -} diff --git a/NearFuture/Preview Content/NearFutureWidgets/AppIntent.swift b/NearFuture/Preview Content/NearFutureWidgets/AppIntent.swift deleted file mode 100644 index 21e1c5b..0000000 --- a/NearFuture/Preview Content/NearFutureWidgets/AppIntent.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// AppIntent.swift -// NearFutureWidgets -// -// Created by neon443 on 02/01/2025. -// - -import WidgetKit -import AppIntents - -struct ConfigurationAppIntent: WidgetConfigurationIntent { - static var title: LocalizedStringResource = "Configuration" - static var description = IntentDescription("This is an example widget.") - - // An example configurable parameter. - @Parameter(title: "Favorite Emoji", default: "😃") - var favoriteEmoji: String -} diff --git a/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/AccentColor.colorset/Contents.json b/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb87897..0000000 --- a/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json b/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 13613e3..0000000 --- a/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/WidgetBackground.colorset/Contents.json b/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/WidgetBackground.colorset/Contents.json deleted file mode 100644 index eb87897..0000000 --- a/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/WidgetBackground.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/NearFuture/Preview Content/NearFutureWidgets/Info.plist b/NearFuture/Preview Content/NearFutureWidgets/Info.plist deleted file mode 100644 index 0f118fb..0000000 --- a/NearFuture/Preview Content/NearFutureWidgets/Info.plist +++ /dev/null @@ -1,11 +0,0 @@ - - - - - NSExtension - - NSExtensionPointIdentifier - com.apple.widgetkit-extension - - - diff --git a/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgets.swift b/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgets.swift deleted file mode 100644 index 9bb45d4..0000000 --- a/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgets.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// NearFutureWidgets.swift -// NearFutureWidgets -// -// Created by neon443 on 02/01/2025. -// - -import WidgetKit -import SwiftUI - -struct Provider: AppIntentTimelineProvider { - func placeholder(in context: Context) -> SimpleEntry { - SimpleEntry(date: Date(), configuration: ConfigurationAppIntent()) - } - - func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> SimpleEntry { - SimpleEntry(date: Date(), configuration: configuration) - } - - func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline { - var entries: [SimpleEntry] = [] - - // Generate a timeline consisting of five entries an hour apart, starting from the current date. - let currentDate = Date() - for hourOffset in 0 ..< 5 { - let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! - let entry = SimpleEntry(date: entryDate, configuration: configuration) - entries.append(entry) - } - - return Timeline(entries: entries, policy: .atEnd) - } -} - -struct SimpleEntry: TimelineEntry { - let date: Date - let configuration: ConfigurationAppIntent -} - -struct NearFutureWidgetsEntryView : View { - var entry: Provider.Entry - - var body: some View { - VStack { - Text("Time:") - Text(entry.date, style: .time) - - Text("Favorite Emoji:") - Text(entry.configuration.favoriteEmoji) - } - } -} - -struct NearFutureWidgets: Widget { - let kind: String = "NearFutureWidgets" - - var body: some WidgetConfiguration { - AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()) { entry in - NearFutureWidgetsEntryView(entry: entry) - .containerBackground(.fill.tertiary, for: .widget) - } - } -} - -extension ConfigurationAppIntent { - fileprivate static var smiley: ConfigurationAppIntent { - let intent = ConfigurationAppIntent() - intent.favoriteEmoji = "😀" - return intent - } - - fileprivate static var starEyes: ConfigurationAppIntent { - let intent = ConfigurationAppIntent() - intent.favoriteEmoji = "🤩" - return intent - } -} - -#Preview(as: .systemSmall) { - NearFutureWidgets() -} timeline: { - SimpleEntry(date: .now, configuration: .smiley) - SimpleEntry(date: .now, configuration: .starEyes) -} diff --git a/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgetsBundle.swift b/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgetsBundle.swift deleted file mode 100644 index 31a0562..0000000 --- a/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgetsBundle.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// NearFutureWidgetsBundle.swift -// NearFutureWidgets -// -// Created by neon443 on 02/01/2025. -// - -import WidgetKit -import SwiftUI - -@main -struct NearFutureWidgetsBundle: WidgetBundle { - var body: some Widget { - NearFutureWidgets() - NearFutureWidgetsLiveActivity() - } -} diff --git a/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgetsLiveActivity.swift b/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgetsLiveActivity.swift deleted file mode 100644 index 3eef6ad..0000000 --- a/NearFuture/Preview Content/NearFutureWidgets/NearFutureWidgetsLiveActivity.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// NearFutureWidgetsLiveActivity.swift -// NearFutureWidgets -// -// Created by neon443 on 02/01/2025. -// - -import ActivityKit -import WidgetKit -import SwiftUI - -struct NearFutureWidgetsAttributes: ActivityAttributes { - public struct ContentState: Codable, Hashable { - // Dynamic stateful properties about your activity go here! - var emoji: String - } - - // Fixed non-changing properties about your activity go here! - var name: String -} - -struct NearFutureWidgetsLiveActivity: Widget { - var body: some WidgetConfiguration { - ActivityConfiguration(for: NearFutureWidgetsAttributes.self) { context in - // Lock screen/banner UI goes here - VStack { - Text("Hello \(context.state.emoji)") - } - .activityBackgroundTint(Color.cyan) - .activitySystemActionForegroundColor(Color.black) - - } dynamicIsland: { context in - DynamicIsland { - // Expanded UI goes here. Compose the expanded UI through - // various regions, like leading/trailing/center/bottom - DynamicIslandExpandedRegion(.leading) { - Text("Leading") - } - DynamicIslandExpandedRegion(.trailing) { - Text("Trailing") - } - DynamicIslandExpandedRegion(.bottom) { - Text("Bottom \(context.state.emoji)") - // more content - } - } compactLeading: { - Text("L") - } compactTrailing: { - Text("T \(context.state.emoji)") - } minimal: { - Text(context.state.emoji) - } - .widgetURL(URL(string: "http://www.apple.com")) - .keylineTint(Color.red) - } - } -} - -extension NearFutureWidgetsAttributes { - fileprivate static var preview: NearFutureWidgetsAttributes { - NearFutureWidgetsAttributes(name: "World") - } -} - -extension NearFutureWidgetsAttributes.ContentState { - fileprivate static var smiley: NearFutureWidgetsAttributes.ContentState { - NearFutureWidgetsAttributes.ContentState(emoji: "😀") - } - - fileprivate static var starEyes: NearFutureWidgetsAttributes.ContentState { - NearFutureWidgetsAttributes.ContentState(emoji: "🤩") - } -} - -#Preview("Notification", as: .content, using: NearFutureWidgetsAttributes.preview) { - NearFutureWidgetsLiveActivity() -} contentStates: { - NearFutureWidgetsAttributes.ContentState.smiley - NearFutureWidgetsAttributes.ContentState.starEyes -} diff --git a/NearFuture/Views/Archive/ArchiveView.swift b/NearFuture/Views/Archive/ArchiveView.swift index 70966fb..8f0fae4 100644 --- a/NearFuture/Views/Archive/ArchiveView.swift +++ b/NearFuture/Views/Archive/ArchiveView.swift @@ -11,7 +11,8 @@ struct ArchiveView: View { @ObservedObject var viewModel: EventViewModel @State var showAddEvent: Bool = false var filteredEvents: [Event] { - return viewModel.events.filter() {$0.complete} + let filteredEvents = viewModel.events.filter({$0.complete}) + return filteredEvents.reversed() } var body: some View { NavigationStack { @@ -22,42 +23,40 @@ struct ArchiveView: View { } else { ScrollView { ForEach(filteredEvents) { event in - EventListView(viewModel: viewModel, event: event) - .transition(.moveAndFadeReversed) - .id(event.complete) + NavigationLink() { + EditEventView( + viewModel: viewModel, + event: Binding( + get: { event }, + set: { newValue in + viewModel.editEvent(newValue) + } + ) + ) + } label: { + EventListView(viewModel: viewModel, event: event) + .id(event.complete) + } + .transition(.moveAndFadeReversed) } .padding(.horizontal) } .animation(.default, value: filteredEvents) } } + .transition(.opacity) .scrollContentBackground(.hidden) .toolbar { - ToolbarItem(placement: .topBarTrailing) { + ToolbarItem(placement: .primaryAction) { AddEventButton(showingAddEventView: $showAddEvent) } } .navigationTitle("Archive") - .apply { - if #available(iOS 17, *) { - $0.toolbarTitleDisplayMode(.inlineLarge) - } else { - $0.navigationBarTitleDisplayMode(.inline) - } - } + .modifier(navigationInlineLarge()) } .sheet(isPresented: $showAddEvent) { AddEventView( - 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 + viewModel: viewModel ) } } diff --git a/NearFuture/Views/Home/ContentView.swift b/NearFuture/Views/ContentView.swift similarity index 53% rename from NearFuture/Views/Home/ContentView.swift rename to NearFuture/Views/ContentView.swift index 122e94b..a0c3e5a 100644 --- a/NearFuture/Views/Home/ContentView.swift +++ b/NearFuture/Views/ContentView.swift @@ -9,12 +9,10 @@ import SwiftUI import UserNotifications import SwiftData -enum Field { - case Search -} enum Tab { case home case archive + case symbols case stats case settings } @@ -22,21 +20,30 @@ enum Tab { struct ContentView: View { @StateObject var viewModel: EventViewModel @StateObject var settingsModel: SettingsViewModel - @State var selection: Tab = .home + @State var tabSelection: Tab = .home var body: some View { - TabView(selection: $selection) { + TabView(selection: $tabSelection) { HomeView(viewModel: viewModel, settingsModel: settingsModel) - .tabItem { - Label("Home", systemImage: "house") - } - .tag(Tab.home) + .tabItem { + Label("Home", systemImage: "house") + } + .tag(Tab.home) ArchiveView(viewModel: viewModel) .tabItem() { Label("Archive", systemImage: "tray.full") } .tag(Tab.archive) + SymbolsPicker( + selection: .constant(""), + browsing: true + ) + .tabItem { + Label("Symbols", systemImage: "star.circle") + } + .tag(Tab.symbols) StatsView(viewModel: viewModel) +// SymbolsPickerStoryboardUIViewRepresentable() .tabItem { Label("Statistics", systemImage: "chart.pie") } @@ -47,11 +54,7 @@ struct ContentView: View { } .tag(Tab.settings) } - .apply { - if #available(iOS 17, *) { - $0.sensoryFeedback(.impact(weight: .heavy, intensity: 1), trigger: selection) - } - } + .modifier(hapticHeavy(trigger: tabSelection)) .sheet(isPresented: $settingsModel.settings.showWhatsNew) { WhatsNewView(settingsModel: settingsModel) } @@ -64,33 +67,3 @@ struct ContentView: View { settingsModel: dummySettingsViewModel() ) } - -extension View { - var backgroundGradient: some View { - return LinearGradient( - gradient: Gradient(colors: [.bgTop, .two]), - startPoint: .top, - endPoint: .bottom - ) - .ignoresSafeArea(.all) - } - - func apply(@ViewBuilder _ block: (Self) -> V) -> V { block(self) } -} - -extension AnyTransition { - static var moveAndFade: AnyTransition { - .asymmetric( - insertion: .move(edge: .leading), - removal: .move(edge: .trailing) - ) - .combined(with: .opacity) - } - static var moveAndFadeReversed: AnyTransition { - .asymmetric( - insertion: .move(edge: .trailing), - removal: .move(edge: .leading) - ) - .combined(with: .opacity) - } -} diff --git a/NearFuture/Views/Events/AddEventView.swift b/NearFuture/Views/Events/AddEventView.swift index 76f14c8..f51f8f4 100644 --- a/NearFuture/Views/Events/AddEventView.swift +++ b/NearFuture/Views/Events/AddEventView.swift @@ -6,23 +6,15 @@ // import SwiftUI -import SFSymbolsPicker struct AddEventView: View { @ObservedObject var viewModel: EventViewModel - @Binding var eventName: String - @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 event: Event = dummyEventViewModel().template - @State var adding: Bool + @State var adding: Bool = true @State var showNeedsNameAlert: Bool = false - @State var isSymbolPickerPresented = false + @State var isSymbolPickerPresented: Bool = false @State private var bye: Bool = false @@ -33,13 +25,21 @@ struct AddEventView: View { @Environment(\.dismiss) var dismiss + var isMac: Bool { + if #available(iOS 1, *) { + return false + } else { + return true + } + } + var body: some View { ZStack { if !adding { backgroundGradient } NavigationStack { - Form { + List { Section( header: Text("Event Details") @@ -51,66 +51,52 @@ struct AddEventView: View { Button() { isSymbolPickerPresented.toggle() } label: { - Image(systemName: eventSymbol) + Image(systemName: event.symbol) .resizable() .scaledToFit() .frame(width: 20, height: 20) - .foregroundStyle(eventColor) + .foregroundStyle(event.color.color) } .frame(width: 20) .buttonStyle(.borderless) .sheet(isPresented: $isSymbolPickerPresented) { SymbolsPicker( - selection: $eventSymbol, - title: "Choose a Symbol", - searchLabel: "Search...", - autoDismiss: true) + selection: $event.symbol + ) .presentationDetents([.medium]) - .apply { - 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) + .modifier(presentationSizeForm()) } + TextField("Event Name", text: $event.name) + .textFieldStyle(.roundedBorder) } // dscription ZStack { - TextField("Event Notes", text: $eventNotes) + TextField("Event Notes", text: $event.notes) .textFieldStyle(RoundedBorderTextFieldStyle()) - .padding(.trailing, eventNotes.isEmpty ? 0 : 30) - .animation(.spring, value: eventNotes) + .padding(.trailing, event.notes.isEmpty ? 0 : 30) + .animation(.spring, value: event.notes) .focused($focusedField, equals: Field.Notes) .submitLabel(.done) .onSubmit { focusedField = nil } - MagicClearButton(text: $eventNotes) } + ColorPicker("Event Color", selection: $event.color.colorBind) // date picker HStack { Spacer() - DatePicker("", selection: $eventDate, displayedComponents: .date) - .datePickerStyle(WheelDatePickerStyle()) + DatePicker("", selection: $event.date, displayedComponents: .date) + #if os(iOS) + .datePickerStyle(.wheel) + #else + .datePickerStyle(.graphical) + #endif Spacer() Button() { - eventDate = Date() + event.date = Date() } label: { Image(systemName: "arrow.uturn.left") .resizable() @@ -122,12 +108,15 @@ struct AddEventView: View { DatePicker( "", - selection: $eventDate, + selection: $event.date, displayedComponents: .hourAndMinute ) + #if os(macOS) + .datePickerStyle(.stepperField) + #endif // re-ocurrence Picker - Picker("Recurrence", selection: $eventRecurrence) { + Picker("Recurrence", selection: $event.recurrence) { ForEach(Event.RecurrenceType.allCases, id: \.self) { recurrence in Text(recurrence.rawValue.capitalized) } @@ -135,61 +124,44 @@ struct AddEventView: View { .pickerStyle(SegmentedPickerStyle()) Text( describeOccurrence( - date: eventDate, - recurrence: eventRecurrence + date: event.date, + recurrence: event.recurrence ) ) } } - .scrollContentBackground(.hidden) .navigationTitle("\(adding ? "Add Event" : "")") - .navigationBarTitleDisplayMode(.inline) + .modifier(navigationInlineLarge()) .toolbar { - ToolbarItem(placement: .topBarLeading) { + ToolbarItem(placement: .cancellationAction) { if adding { Button() { resetAddEventView() dismiss() } label: { - Image(systemName: "xmark.circle.fill") - .symbolRenderingMode(.hierarchical) + Image(systemName: "xmark") .resizable() .scaledToFit() - .frame(width: 30) + .tint(.one) } } } - ToolbarItem(placement: .topBarTrailing) { + ToolbarItem() { if adding { Button { viewModel.addEvent( - newEvent: Event( - name: eventName, - complete: eventComplete, - completeDesc: eventCompleteDesc, - symbol: eventSymbol, - color: ColorCodable(eventColor), - notes: eventNotes, - date: eventDate, - recurrence: eventRecurrence - ) + newEvent: event ) bye.toggle() resetAddEventView() } label: { - Text("Save") - .font(.headline) - .cornerRadius(10) - .buttonStyle(BorderedProminentButtonStyle()) + Label("Save", systemImage: "checkmark") } - .apply { - if #available(iOS 17, *) { - $0.sensoryFeedback(.success, trigger: bye) - } - } - .disabled(eventName.isEmpty) + .tint(.accent) + .modifier(hapticSuccess(trigger: bye)) + .disabled(event.name.isEmpty) .onTapGesture { - if eventName.isEmpty { + if event.name.isEmpty { showNeedsNameAlert.toggle() } } @@ -201,36 +173,29 @@ struct AddEventView: View { } message: { Text("Give your Event a name before saving.") } - if eventName.isEmpty { - HStack { - Image(systemName: "exclamationmark") - .foregroundStyle(.red) - Text("Give your event a name.") - } + } + } + ToolbarItem(placement: .confirmationAction) { + if !adding { + Button() { + viewModel.editEvent(event) + dismiss() + } label: { + Label("Done", systemImage: "checkmark") } + .disabled(event.name == "") } } } + .navigationTitle("Editing \(event.name) - Ne") } + .scrollContentBackground(isMac ? .automatic : .hidden) .presentationDragIndicator(.visible) - .scrollContentBackground(.hidden) - } - .apply { - if #available(iOS 16.4, *) { - $0.presentationBackground(.ultraThinMaterial) - } } } func resetAddEventView() { //reset addeventView - eventName = viewModel.template.name - 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 + event = viewModel.template dismiss() } @@ -243,14 +208,6 @@ struct AddEventView: View { .sheet(isPresented: .constant(true)) { AddEventView( 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 ) } diff --git a/NearFuture/Views/Events/EditEventView.swift b/NearFuture/Views/Events/EditEventView.swift index 0cea627..6962920 100644 --- a/NearFuture/Views/Events/EditEventView.swift +++ b/NearFuture/Views/Events/EditEventView.swift @@ -12,44 +12,13 @@ struct EditEventView: View { @ObservedObject var viewModel: EventViewModel @Binding var event: Event - fileprivate func saveEdits() { - //if there is an event in vM.events with the id of the event we r editing, - //firstindex - loops through the arr and finds first element where that events id matches editing event's id - if let index = viewModel.events.firstIndex(where: { xEvent in - xEvent.id == event.id - }) { - viewModel.events[index] = event - } - viewModel.saveEvents() - - dismiss() - } - var body: some View { AddEventView( viewModel: viewModel, - 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, + event: event, adding: false //bc we editing existing event ) .navigationTitle("Edit Event") - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - Button() { - saveEdits() - } label: { - Text("Done") - .bold() - } - .disabled(event.name == "") - } - } } } diff --git a/NearFuture/Views/Home/EventListView.swift b/NearFuture/Views/Home/EventListView.swift index 32c393c..5ec6a8c 100644 --- a/NearFuture/Views/Home/EventListView.swift +++ b/NearFuture/Views/Home/EventListView.swift @@ -12,141 +12,185 @@ struct EventListView: View { @ObservedObject var viewModel: EventViewModel @State var event: Event + @Environment(\.openWindow) var openWindow + + @State var hovering: Bool = false + +#if canImport(AppKit) var body: some View { - NavigationLink() { - EditEventView( - viewModel: viewModel, - event: $event - ) - } label: { - ZStack { - HStack { - RoundedRectangle(cornerRadius: 5) - .frame(width: 7) - .foregroundStyle( - event.color.color.opacity( - event.complete ? 0.5 : 1 - ) + ZStack { + Color.black.opacity(hovering ? 0.5 : 0.0) + HStack { + RoundedRectangle(cornerRadius: 5) + .frame(width: 7) + .foregroundStyle( + event.color.color.opacity( + event.complete ? 0.5 : 1 ) - VStack(alignment: .leading) { - HStack { - Image(systemName: event.symbol) - .resizable() - .scaledToFit() - .frame(width: 20, height: 20) - .shadow(radius: 5) - .foregroundStyle( - .one.opacity( - event.complete ? 0.5 : 1 - ) + ) + VStack(alignment: .leading) { + HStack { + Image(systemName: event.symbol) + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .shadow(radius: 5) + .foregroundStyle( + .one.opacity( + event.complete ? 0.5 : 1 ) - Text("\(event.name)") - .font(.headline) - .foregroundStyle(.one) - .strikethrough(event.complete) - .multilineTextAlignment(.leading) - } - if !event.notes.isEmpty { - Text(event.notes) - .font(.subheadline) - .foregroundStyle(.one.opacity(0.8)) - .multilineTextAlignment(.leading) - } - Text( - event.date.formatted( - date: .long, - time: .shortened ) + Text("\(event.name)") + .bold() + .foregroundStyle(.one) + .strikethrough(event.complete) + .multilineTextAlignment(.leading) + } + if !event.notes.isEmpty { + Text(event.notes) + .foregroundStyle(.one.opacity(0.8)) + .multilineTextAlignment(.leading) + } + Text( + event.date.formatted( + date: .long, + time: .shortened ) - .font(.subheadline) + ) + .foregroundStyle( + .one.opacity( + event.complete ? 0.5 : 1 + ) + ) + if event.recurrence != .none { + Text("Occurs \(event.recurrence.rawValue)") + .font(.subheadline) + .foregroundStyle( + .one.opacity(event.complete ? 0.5 : 1)) + } + } + Spacer() + VStack { + Text("\(daysUntilEvent(event.date).long)") + .multilineTextAlignment(.trailing) + .foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one) + } + CompleteEventButton( + viewModel: viewModel, + event: $event + ) + } + .fixedSize(horizontal: false, vertical: true) + } + .onHover { isHovering in + withAnimation { + hovering.toggle() + } + } + .onTapGesture { + openWindow(value: event.id) + } + .contextMenu() { + Button(role: .destructive) { + let eventToModify = viewModel.events.firstIndex() { currEvent in + currEvent.id == event.id + } + if let eventToModify = eventToModify { + viewModel.events.remove(at: eventToModify) + viewModel.saveEvents() + } + } label: { + Label("Delete", systemImage: "trash") + } + } + } +#else + var body: some View { + HStack { + RoundedRectangle(cornerRadius: 5) + .frame(width: 7) + .foregroundStyle( + event.color.color.opacity( + event.complete ? 0.5 : 1 + ) + ) + VStack(alignment: .leading) { + HStack { + Image(systemName: event.symbol) + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .shadow(radius: 5) .foregroundStyle( .one.opacity( event.complete ? 0.5 : 1 ) ) - if event.recurrence != .none { - Text("Occurs \(event.recurrence.rawValue)") - .font(.subheadline) - .foregroundStyle( - .one.opacity(event.complete ? 0.5 : 1)) - } - } - Spacer() - VStack { - Text("\(daysUntilEvent(event.date).long)") - .font(.subheadline) - .foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one) - } - Button() { - withAnimation { - event.complete.toggle() - } - let eventToModify = viewModel.events.firstIndex() { currEvent in - currEvent.id == event.id - } - if let eventToModify = eventToModify { - viewModel.events[eventToModify] = event - viewModel.saveEvents() - } - } label: { - if event.complete { - ZStack { - Circle() - .foregroundStyle(.green) - Image(systemName: "checkmark") - .resizable() - .foregroundStyle(.white) - .scaledToFit() - .bold() - .frame(width: 15) - } - } else { - Image(systemName: "circle") - .resizable() - .scaledToFit() - .foregroundStyle(event.color.color) - } - } - .buttonStyle(.borderless) - .frame(maxWidth: 25, maxHeight: 25) - .shadow(radius: 5) - .padding(.trailing, 5) - .apply { - if #available(iOS 17, *) { - $0.sensoryFeedback(.success, trigger: event.complete) - } - } + Text("\(event.name)") + .font(.headline) + .foregroundStyle(.one) + .strikethrough(event.complete) + .multilineTextAlignment(.leading) } - .transition(.opacity) - .padding(.vertical, 5) - .background(.ultraThinMaterial) - .overlay( - RoundedRectangle(cornerRadius: 10) - .stroke( - .one.opacity(0.5), - lineWidth: 1 - ) + if !event.notes.isEmpty { + Text(event.notes) + .font(.subheadline) + .foregroundStyle(.one.opacity(0.8)) + .multilineTextAlignment(.leading) + } + Text( + event.date.formatted( + date: .long, + time: .shortened + ) ) - .clipShape( - RoundedRectangle(cornerRadius: 10) + .font(.subheadline) + .foregroundStyle( + .one.opacity( + event.complete ? 0.5 : 1 + ) ) - .fixedSize(horizontal: false, vertical: true) + if event.recurrence != .none { + Text("Occurs \(event.recurrence.rawValue)") + .font(.subheadline) + .foregroundStyle( + .one.opacity(event.complete ? 0.5 : 1)) + } } - .contextMenu() { - Button(role: .destructive) { - let eventToModify = viewModel.events.firstIndex() { currEvent in - currEvent.id == event.id - } - if let eventToModify = eventToModify { - viewModel.events.remove(at: eventToModify) - viewModel.saveEvents() - } - } label: { - Label("Delete", systemImage: "trash") + Spacer() + VStack { + Text("\(daysUntilEvent(event.date).long)") + .font(.subheadline) + .foregroundStyle(event.date.timeIntervalSinceNow < 0 ? .red : .one) + .multilineTextAlignment(.trailing) + } + CompleteEventButton( + viewModel: viewModel, + event: $event + ) + } + .padding(.vertical, 5) + .overlay( + RoundedRectangle(cornerRadius: 15) + .stroke(.one.opacity(0.5), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 15)) + .fixedSize(horizontal: false, vertical: true) + .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") } } } +#endif } #Preview("EventListView") { diff --git a/NearFuture/Views/Home/HelpView.swift b/NearFuture/Views/Home/HelpView.swift index 24d4909..3958e8d 100644 --- a/NearFuture/Views/Home/HelpView.swift +++ b/NearFuture/Views/Home/HelpView.swift @@ -13,6 +13,10 @@ enum HelpType { case Archive } +enum Field { + case Search +} + struct HelpView: View { /// initialises a Search HelpView /// @@ -74,7 +78,7 @@ struct HelpView: View { var body: some View { List { ZStack { - Color(.tintColor) + Color(.accent) .opacity(0.4) .padding(.horizontal, -15) .blur(radius: 5) diff --git a/NearFuture/Views/Home/HomeView.swift b/NearFuture/Views/Home/HomeView.swift index 56741cf..8f77c9e 100644 --- a/NearFuture/Views/Home/HomeView.swift +++ b/NearFuture/Views/Home/HomeView.swift @@ -5,20 +5,14 @@ // Created by neon443 on 12/05/2025. // -import SwiftUI;import AppIntents +import SwiftUI +import AppIntents struct HomeView: View { @ObservedObject var viewModel: EventViewModel @ObservedObject var settingsModel: SettingsViewModel - @State private var eventName = "" - @State private var eventComplete = false - @State private var eventCompleteDesc = "" - @State private var eventSymbol = "star" - @State private var eventColor: Color = randomColor() - @State private var eventNotes = "" - @State private var eventDate = Date() - @State private var eventRecurrence: Event.RecurrenceType = .none - @State private var showingAddEventView = false + + @State private var showingAddEventView: Bool = false @State private var searchInput: String = "" @Environment(\.colorScheme) var appearance var darkMode: Bool { @@ -47,36 +41,30 @@ struct HomeView: View { ZStack { backgroundGradient 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 { HelpView(searchInput: $searchInput, focusedField: focusedField) } else { ScrollView { - ForEach(filteredEvents) { event in - EventListView(viewModel: viewModel, event: event) +// LazyVStack { + ForEach(filteredEvents) { event in + NavigationLink() { + EditEventView( + viewModel: viewModel, + event: Binding( + get: { event }, + set: { newValue in + viewModel.editEvent(newValue) + } + ) + ) + } label: { + EventListView(viewModel: viewModel, event: event) + .id(event.complete) + } .transition(.moveAndFade) - .id(event.complete) - } - .padding(.horizontal) + } + .padding(.horizontal) +// } if filteredEvents.isEmpty { HelpView( searchInput: $searchInput, @@ -88,30 +76,16 @@ struct HomeView: View { .animation(.default, value: filteredEvents) } } + .searchable(text: $searchInput) .navigationTitle("Near Future") - .apply { - if #available(iOS 17, *) { - $0.toolbarTitleDisplayMode(.inlineLarge) - } else { - $0.navigationBarTitleDisplayMode(.inline) - } - } + .modifier(navigationInlineLarge()) .sheet(isPresented: $showingAddEventView) { AddEventView( - viewModel: viewModel, - eventName: $eventName, - eventComplete: $eventComplete, - eventCompleteDesc: $eventCompleteDesc, - eventSymbol: $eventSymbol, - eventColor: $eventColor, - eventNotes: $eventNotes, - eventDate: $eventDate, - eventRecurrence: $eventRecurrence, - adding: true //adding event + viewModel: viewModel ) } .toolbar { - ToolbarItem(placement: .topBarTrailing) { + ToolbarItem(placement: .primaryAction) { AddEventButton(showingAddEventView: $showingAddEventView) } } diff --git a/NearFuture/Views/Misc/Buttons.swift b/NearFuture/Views/Misc/Buttons.swift index 48a447b..313a631 100644 --- a/NearFuture/Views/Misc/Buttons.swift +++ b/NearFuture/Views/Misc/Buttons.swift @@ -34,17 +34,12 @@ struct AddEventButton: View { Button() { showingAddEventView.toggle() } label: { - ZStack { - Circle() - .frame(width: 33) - .foregroundStyle(.one) - Image(systemName: "plus") - .resizable() - .scaledToFit() - .frame(width: 15) - .bold() - .foregroundStyle(.two) - } + Image(systemName: "plus") + .resizable() + .scaledToFit() + .frame(width: 15) + .bold() + .foregroundStyle(.one) } } } diff --git a/NearFuture/Views/Settings/ExportView.swift b/NearFuture/Views/Settings/ExportView.swift index ef30633..9577b41 100644 --- a/NearFuture/Views/Settings/ExportView.swift +++ b/NearFuture/Views/Settings/ExportView.swift @@ -12,7 +12,11 @@ struct ExportView: View { var body: some View { List { Button() { + #if canImport(UIKit) UIPasteboard.general.string = viewModel.exportEvents() + #else + NSPasteboard.general.setString(viewModel.exportEvents(), forType: .string) + #endif } label: { Label("Copy Events", systemImage: "document.on.clipboard") } diff --git a/NearFuture/Views/Settings/ImportView.swift b/NearFuture/Views/Settings/ImportView.swift index 6694076..b6cda9d 100644 --- a/NearFuture/Views/Settings/ImportView.swift +++ b/NearFuture/Views/Settings/ImportView.swift @@ -15,47 +15,117 @@ struct ImportView: View { @State private var text: String = "Ready..." @State private var fgColor: Color = .yellow - var body: some View { - List { - Section("Status") { - Label(text, systemImage: image) - .contentTransition(.numericText()) - .foregroundStyle(fgColor) + @State private var showAlert: Bool = false + + @State private var replaceCurrentEvents: Bool = false + + fileprivate func importEvents() { + do throws { + try viewModel.importEvents(importStr, replace: replaceCurrentEvents) + withAnimation { + image = "checkmark.circle.fill" + text = "Complete" + fgColor = .green } - TextField("", text: $importStr) - Button() { - do throws { - try viewModel.importEvents(importStr) - withAnimation { - image = "checkmark.circle.fill" - text = "Complete" - fgColor = .green - } - } catch importError.invalidB64 { - withAnimation { - image = "xmark.app.fill" - text = "Invalid base64 input." - fgColor = .red - } - } catch { - withAnimation { - image = "xmark.app.fill" - text = error.localizedDescription - fgColor = .red - } - } - } label: { - Label("Import", systemImage: "tray.and.arrow.down.fill") + } catch importError.invalidB64 { + withAnimation { + image = "xmark.app.fill" + text = "Invalid base64 input." + fgColor = .red } - .disabled(importStr.isEmpty) - .onAppear() { - importStr = "" - image = "clock.fill" - text = "Ready..." - fgColor = .yellow + } catch { + withAnimation { + image = "xmark.app.fill" + text = error.localizedDescription + fgColor = .red } } - + } + + var body: some View { + ZStack(alignment: .center) { + List { + Section("Status") { + Label(text, systemImage: image) + .contentTransition(.numericText()) + .foregroundStyle(fgColor) + } + TextField("", text: $importStr) + Button() { + withAnimation { + showAlert.toggle() + } + } label: { + Label("Import", systemImage: "tray.and.arrow.down.fill") + } + .disabled(importStr.isEmpty) + .onAppear() { + importStr = "" + image = "clock.fill" + text = "Ready..." + fgColor = .yellow + } + } + .blur(radius: showAlert ? 2 : 0) + Group { + Rectangle() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .foregroundStyle(replaceCurrentEvents ? .red.opacity(0.25) : .black.opacity(0.2)) + .animation(.default, value: replaceCurrentEvents) + .ignoresSafeArea() + ZStack { + Rectangle() + .clipShape(RoundedRectangle(cornerRadius: 25)) + VStack(alignment: .center) { + Text("Are you sure?") + .font(.largeTitle) + .bold() + .foregroundStyle(replaceCurrentEvents ? .red : .two) + .animation(.default, value: replaceCurrentEvents) + Text("This will replace your current events!") + .lineLimit(nil) + .multilineTextAlignment(.center) + .opacity(replaceCurrentEvents ? 1 : 0) + .animation(.default, value: replaceCurrentEvents) + .foregroundStyle(.two) + Toggle("Replace Events", isOn: $replaceCurrentEvents) + .foregroundStyle(.two) + Spacer() + HStack { + Button() { + withAnimation { + showAlert.toggle() + } + importEvents() + } label: { + Text("cancel") + .font(.title2) + .bold() + } + .buttonStyle(BorderedProminentButtonStyle()) + + Spacer() + + Button() { + withAnimation { + showAlert.toggle() + } + importEvents() + } label: { + Text("yes") + .font(.title2) + .bold() + } + .buttonStyle(BorderedProminentButtonStyle()) + } + .padding() + } + .padding() + } + .frame(maxWidth: 250, maxHeight: 250) + } + .opacity(showAlert ? 1 : 0) + } } } diff --git a/NearFuture/Views/Settings/SettingsView.swift b/NearFuture/Views/Settings/SettingsView.swift index 9c3fcd7..e6a5fba 100644 --- a/NearFuture/Views/Settings/SettingsView.swift +++ b/NearFuture/Views/Settings/SettingsView.swift @@ -10,48 +10,21 @@ import SwiftUI struct SettingsView: View { @ObservedObject var viewModel: EventViewModel @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 = "" - func updateStatus() { - 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) { + func changeIcon(to toIcon: String) { guard UIApplication.shared.supportsAlternateIcons else { print("doesnt tsupport alternate icons") return } - guard to != "orange" else { + guard toIcon != "orange" else { UIApplication.shared.setAlternateIconName(nil) { error in print(error as Any) } return } - UIApplication.shared.setAlternateIconName(to) { error in + UIApplication.shared.setAlternateIconName(toIcon) { error in print(error as Any) } } @@ -74,6 +47,7 @@ struct SettingsView: View { .foregroundStyle(color) .frame(width: 30) } + .buttonStyle(.plain) if ColorCodable(color) == settingsModel.settings.tint { let needContrast: Bool = ColorCodable(color) == settingsModel.settings.tint Circle() @@ -94,13 +68,16 @@ struct SettingsView: View { NavigationLink() { List { if !settingsModel.notifsGranted { - Button("Request Notifications") { - Task { - settingsModel.notifsGranted = await requestNotifs() - } - } 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) @@ -113,13 +90,7 @@ struct SettingsView: View { NavigationLink() { iCloudSettingsView( viewModel: viewModel, - settingsModel: settingsModel, - hasUbiquitous: $hasUbiquitous, - lastSyncWasSuccessful: $lastSyncWasSuccessful, - lastSyncWasNormalAgo: $lastSyncWasNormalAgo, - localCountEqualToiCloud: $localCountEqualToiCloud, - icloudCountEqualToLocal: $icloudCountEqualToLocal, - updateStatus: updateStatus + settingsModel: settingsModel ) } label: { HStack { @@ -128,12 +99,12 @@ struct SettingsView: View { Spacer() Circle() .frame(width: 20, height: 20) - .foregroundStyle(iCloudStatusColor) + .foregroundStyle(viewModel.iCloudStatusColor) } } .onAppear { viewModel.sync() - updateStatus() + viewModel.updateiCStatus() } NavigationLink() { ImportView(viewModel: viewModel, importStr: $importStr) @@ -177,15 +148,9 @@ struct SettingsView: View { } } } - .scrollContentBackground(.hidden) .navigationTitle("Settings") - .apply { - if #available(iOS 17, *) { - $0.toolbarTitleDisplayMode(.inlineLarge) - } else { - $0.navigationBarTitleDisplayMode(.inline) - } - } + .modifier(navigationInlineLarge()) + .scrollContentBackground(.hidden) } } } diff --git a/NearFuture/Views/Settings/WhatsNewView.swift b/NearFuture/Views/Settings/WhatsNewView.swift index efa8350..ca709d1 100644 --- a/NearFuture/Views/Settings/WhatsNewView.swift +++ b/NearFuture/Views/Settings/WhatsNewView.swift @@ -16,9 +16,14 @@ struct WhatsNewView: View { var title: String var subtitle: String } - @State var bye = false + @State var bye: Bool = false var whatsNewChunks: [WhatsNewChunk] { return [ + WhatsNewChunk( + symbol: "desktopcomputer", + title: "Mac Native App", + subtitle: "New Mac native app (Intel too!)" + ), WhatsNewChunk( symbol: "iphone.radiowaves.left.and.right", title: "Haptic Feedback", @@ -53,23 +58,22 @@ struct WhatsNewView: View { } var body: some View { NavigationStack { - List { - VStack { - Text("What's New") - .font(.largeTitle) - .bold() - AboutView() - Divider() - VStack(alignment: .leading) { - ForEach(whatsNewChunks) { new in - WhatsNewChunkView( - symbol: new.symbol, - title: new.title, - subtitle: new.subtitle - ) - } + ScrollView { + Text("What's New") + .font(.largeTitle) + .bold() + .padding(.vertical) + VStack(alignment: .leading) { + ForEach(whatsNewChunks) { new in + WhatsNewChunkView( + symbol: new.symbol, + title: new.title, + subtitle: new.subtitle + ) } + Spacer() } + .padding(.horizontal, 10) } Button() { bye.toggle() @@ -79,24 +83,14 @@ struct WhatsNewView: View { .font(.headline) .frame(height: 40) .bold() - .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) - } +// .frame(maxWidth: .infinity) } + .foregroundStyle(.orange) + .modifier(glassButton()) + .modifier(hapticHeavy(trigger: bye)) } .scrollContentBackground(.hidden) .presentationDragIndicator(.visible) - .apply { - if #available(iOS 16.4, *) { - $0.presentationBackground(.ultraThinMaterial) - } - } .onDisappear { settingsModel.settings.prevAppVersion = getVersion()+getBuildID() settingsModel.saveSettings() @@ -122,7 +116,7 @@ struct WhatsNewChunkView: View { .resizable() .scaledToFit() .frame(width: 30, height: 30) - .foregroundStyle(Color.accentColor) + .foregroundStyle(Color.orange) .padding(.trailing, 15) VStack(alignment: .leading) { Text(title) diff --git a/NearFuture/Views/Settings/iCloudSettingsView.swift b/NearFuture/Views/Settings/iCloudSettingsView.swift index 973e803..72cb561 100644 --- a/NearFuture/Views/Settings/iCloudSettingsView.swift +++ b/NearFuture/Views/Settings/iCloudSettingsView.swift @@ -13,13 +13,13 @@ struct iCloudSettingsView: View { @State var showPushAlert: Bool = false @State var showPullAlert: Bool = false - @Binding var hasUbiquitous: Bool - @Binding var lastSyncWasSuccessful: Bool - @Binding var lastSyncWasNormalAgo: Bool - @Binding var localCountEqualToiCloud: Bool - @Binding var icloudCountEqualToLocal: Bool - - var updateStatus: () -> Void +// @Binding var hasUbiquitous: Bool +// @Binding var lastSyncWasSuccessful: Bool +// @Binding var lastSyncWasNormalAgo: Bool +// @Binding var localCountEqualToiCloud: Bool +// @Binding var icloudCountEqualToLocal: Bool +// +// var updateStatus: () -> Void var body: some View { ZStack { @@ -55,7 +55,7 @@ struct iCloudSettingsView: View { Button("OK", role: .destructive) { viewModel.replaceiCloudWithLocalData() viewModel.sync() - updateStatus() + viewModel.updateiCStatus() } Button("Cancel", role: .cancel) {} } message: { @@ -64,7 +64,7 @@ struct iCloudSettingsView: View { Button() { viewModel.sync() - updateStatus() + viewModel.updateiCStatus() } label: { Image(systemName: "arrow.triangle.2.circlepath") .resizable() @@ -87,7 +87,7 @@ struct iCloudSettingsView: View { Button("OK", role: .destructive) { viewModel.replaceLocalWithiCloudData() viewModel.sync() - updateStatus() + viewModel.updateiCStatus() } Button("Cancel", role: .cancel) {} } message: { @@ -112,23 +112,23 @@ struct iCloudSettingsView: View { .listRowSeparator(.hidden) .onAppear { viewModel.sync() - updateStatus() + viewModel.updateiCStatus() } HStack { Circle() .frame(width: 20, height: 20) - .foregroundStyle(hasUbiquitous ? .green : .red) + .foregroundStyle(viewModel.hasUbiquitous ? .green : .red) Text("iCloud") Spacer() - Text("\(hasUbiquitous ? "" : "Not ")Working") + Text("\(viewModel.hasUbiquitous ? "" : "Not ")Working") .bold() } HStack { Circle() .frame(width: 20, height: 20) - .foregroundStyle(lastSyncWasSuccessful ? .green : .red) + .foregroundStyle(viewModel.lastSyncWasSuccessful ? .green : .red) Text("Sync Status") Spacer() Text("\(viewModel.syncStatus)") @@ -138,7 +138,7 @@ struct iCloudSettingsView: View { HStack { Circle() .frame(width: 20, height: 20) - .foregroundStyle(lastSyncWasNormalAgo ? .green : .red) + .foregroundStyle(viewModel.lastSyncWasNormalAgo ? .green : .red) Text("Last Sync") Spacer() Text("\(viewModel.lastSync?.formatted(date: .long, time: .standard) ?? "Never")") @@ -148,7 +148,7 @@ struct iCloudSettingsView: View { HStack { Circle() .frame(width: 20, height: 20) - .foregroundStyle(localCountEqualToiCloud ? .green : .red) + .foregroundStyle(viewModel.localCountEqualToiCloud ? .green : .red) Text("Local Events") Spacer() Text("\(viewModel.localEventCount)") @@ -158,7 +158,7 @@ struct iCloudSettingsView: View { HStack { Circle() .frame(width: 20, height: 20) - .foregroundStyle(icloudCountEqualToLocal ? .green : .red) + .foregroundStyle(viewModel.icloudCountEqualToLocal ? .green : .red) Text("Events in iCloud") Spacer() Text("\(viewModel.icloudEventCount)") @@ -172,11 +172,10 @@ struct iCloudSettingsView: View { } .refreshable { viewModel.sync() - updateStatus() + viewModel.updateiCStatus() } .scrollContentBackground(.hidden) .navigationTitle("iCloud") - .navigationBarTitleDisplayMode(.inline) } } } @@ -184,12 +183,6 @@ struct iCloudSettingsView: View { #Preview("iCloudSettingsView") { iCloudSettingsView( viewModel: dummyEventViewModel(), - settingsModel: dummySettingsViewModel(), - hasUbiquitous: .constant(true), - lastSyncWasSuccessful: .constant(true), - lastSyncWasNormalAgo: .constant(true), - localCountEqualToiCloud: .constant(true), - icloudCountEqualToLocal: .constant(true), - updateStatus: {} + settingsModel: dummySettingsViewModel() ) } diff --git a/NearFuture/Views/Stats/StatsView.swift b/NearFuture/Views/Stats/StatsView.swift index 588e121..eae317e 100644 --- a/NearFuture/Views/Stats/StatsView.swift +++ b/NearFuture/Views/Stats/StatsView.swift @@ -58,13 +58,7 @@ struct StatsView: View { } .scrollContentBackground(.hidden) .navigationTitle("Statistics") - .apply { - if #available(iOS 17, *) { - $0.toolbarTitleDisplayMode(.inlineLarge) - } else { - $0.navigationBarTitleDisplayMode(.inline) - } - } + .modifier(navigationInlineLarge()) } } } diff --git a/NearFutureTests/NearFutureTests.swift b/NearFutureTests/NearFutureTests.swift deleted file mode 100644 index 06165fc..0000000 --- a/NearFutureTests/NearFutureTests.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// NearFutureTests.swift -// NearFutureTests -// -// Created by neon443 on 26/03/2025. -// - -import Testing - -struct NearFutureTests { - - @Test func example() async throws { - // Write your test here and use APIs like `#expect(...)` to check expected conditions. - } - -} diff --git a/README.md b/README.md index 0048fa8..99f2c47 100644 --- a/README.md +++ b/README.md @@ -29,17 +29,18 @@ - [x] Event colors - [x] Recurrence - [x] Search -- [ ] Notifications +- [x] Notifications +- [ ] Mac App - [ ] Apple Watch App - [x] Home Screen Widgets - [ ] Lock Screen Widgets - [ ] Later Box - [ ] Sort by - [ ] Reorder Events -- [ ] Archive +- [x] Archive - [ ] Collaboration - [ ] Autocomplete tasks -- [ ] Settings +- [x] Settings ## Features - **Event Creation**: Create events with a name, description, date, recurrence, and an icon diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json index 1e9333b..6f663e8 100644 --- a/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -31,54 +31,70 @@ "size" : "1024x1024" }, { + "filename" : "MacNearFutureIcon16.png", "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { + "filename" : "MacNearFutureIcon32.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { + "filename" : "MacNearFutureIcon32.png", "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { + "filename" : "MacNearFutureIcon64.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { + "filename" : "MacNearFutureIcon128.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { + "filename" : "MacNearFutureIcon256.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { + "filename" : "MacNearFutureIcon256.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { + "filename" : "MacNearFutureIcon512.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { + "filename" : "MacNearFutureIcon512.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { + "filename" : "MacNearFutureIcon.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" + }, + { + "filename" : "NearFutureIcon.png", + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" } ], "info" : { diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon.png b/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon.png new file mode 100644 index 0000000..e479390 Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon.png differ diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon128.png b/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon128.png new file mode 100644 index 0000000..07930d2 Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon128.png differ diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon16.png b/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon16.png new file mode 100644 index 0000000..18566cc Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon16.png differ diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon256.png b/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon256.png new file mode 100644 index 0000000..0cf18ea Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon256.png differ diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon32.png b/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon32.png new file mode 100644 index 0000000..40baa3e Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon32.png differ diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon512.png b/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon512.png new file mode 100644 index 0000000..6bab581 Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon512.png differ diff --git a/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon64.png b/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon64.png new file mode 100644 index 0000000..95b823f Binary files /dev/null and b/Resources/Assets.xcassets/AppIcon.appiconset/MacNearFutureIcon64.png differ diff --git a/Resources/Assets.xcassets/bloo.appiconset/Contents.json b/Resources/Assets.xcassets/bloo.appiconset/Contents.json index 5e8bb49..ee8f596 100644 --- a/Resources/Assets.xcassets/bloo.appiconset/Contents.json +++ b/Resources/Assets.xcassets/bloo.appiconset/Contents.json @@ -29,6 +29,12 @@ "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" + }, + { + "filename" : "bloo.png", + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" } ], "info" : { diff --git a/Resources/Assets.xcassets/blue.appiconset/Contents.json b/Resources/Assets.xcassets/blue.appiconset/Contents.json index 685a600..5a7fd5f 100644 --- a/Resources/Assets.xcassets/blue.appiconset/Contents.json +++ b/Resources/Assets.xcassets/blue.appiconset/Contents.json @@ -29,6 +29,12 @@ "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" + }, + { + "filename" : "blue.png", + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" } ], "info" : { diff --git a/Resources/Assets.xcassets/green.appiconset/Contents.json b/Resources/Assets.xcassets/green.appiconset/Contents.json index 81b9d10..a14471a 100644 --- a/Resources/Assets.xcassets/green.appiconset/Contents.json +++ b/Resources/Assets.xcassets/green.appiconset/Contents.json @@ -29,6 +29,12 @@ "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" + }, + { + "filename" : "green.png", + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" } ], "info" : { diff --git a/Resources/Assets.xcassets/pink.appiconset/Contents.json b/Resources/Assets.xcassets/pink.appiconset/Contents.json index 15df7dc..3093a8a 100644 --- a/Resources/Assets.xcassets/pink.appiconset/Contents.json +++ b/Resources/Assets.xcassets/pink.appiconset/Contents.json @@ -29,6 +29,12 @@ "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" + }, + { + "filename" : "pink.png", + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" } ], "info" : { diff --git a/Resources/Assets.xcassets/purple.appiconset/Contents.json b/Resources/Assets.xcassets/purple.appiconset/Contents.json index f75afde..d6161f4 100644 --- a/Resources/Assets.xcassets/purple.appiconset/Contents.json +++ b/Resources/Assets.xcassets/purple.appiconset/Contents.json @@ -29,6 +29,12 @@ "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" + }, + { + "filename" : "purple.png", + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" } ], "info" : { diff --git a/Resources/Assets.xcassets/red.appiconset/Contents.json b/Resources/Assets.xcassets/red.appiconset/Contents.json index eb3ee18..a33e2c5 100644 --- a/Resources/Assets.xcassets/red.appiconset/Contents.json +++ b/Resources/Assets.xcassets/red.appiconset/Contents.json @@ -29,6 +29,12 @@ "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" + }, + { + "filename" : "red.png", + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" } ], "info" : { diff --git a/Resources/Assets.xcassets/yellow.appiconset/Contents.json b/Resources/Assets.xcassets/yellow.appiconset/Contents.json index ae6c02e..87f4f9c 100644 --- a/Resources/Assets.xcassets/yellow.appiconset/Contents.json +++ b/Resources/Assets.xcassets/yellow.appiconset/Contents.json @@ -29,6 +29,12 @@ "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" + }, + { + "filename" : "yellow.png", + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" } ], "info" : { diff --git a/Resources/MacNearFutureIcon.pxd b/Resources/MacNearFutureIcon.pxd new file mode 100644 index 0000000..01b0d17 Binary files /dev/null and b/Resources/MacNearFutureIcon.pxd differ diff --git a/Resources/NearFuture.icon/Assets/Oval copy.png b/Resources/NearFuture.icon/Assets/Oval copy.png new file mode 100644 index 0000000..f377ee4 Binary files /dev/null and b/Resources/NearFuture.icon/Assets/Oval copy.png differ diff --git a/Resources/NearFuture.icon/Assets/Rounded Rectangle.png b/Resources/NearFuture.icon/Assets/Rounded Rectangle.png new file mode 100644 index 0000000..7dadcb0 Binary files /dev/null and b/Resources/NearFuture.icon/Assets/Rounded Rectangle.png differ diff --git a/Resources/NearFuture.icon/icon.json b/Resources/NearFuture.icon/icon.json new file mode 100644 index 0000000..8a60fd5 --- /dev/null +++ b/Resources/NearFuture.icon/icon.json @@ -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" + } +} \ No newline at end of file diff --git a/NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/Contents.json b/Resources/Tints.xcassets/Contents.json similarity index 100% rename from NearFuture/Preview Content/NearFutureWidgets/Assets.xcassets/Contents.json rename to Resources/Tints.xcassets/Contents.json diff --git a/Resources/Assets.xcassets/one.colorset/Contents.json b/Resources/Tints.xcassets/one.colorset/Contents.json similarity index 100% rename from Resources/Assets.xcassets/one.colorset/Contents.json rename to Resources/Tints.xcassets/one.colorset/Contents.json diff --git a/Resources/Assets.xcassets/two.colorset/Contents.json b/Resources/Tints.xcassets/two.colorset/Contents.json similarity index 100% rename from Resources/Assets.xcassets/two.colorset/Contents.json rename to Resources/Tints.xcassets/two.colorset/Contents.json diff --git a/Resources/Assets.xcassets/uiColors/Contents.json b/Resources/Tints.xcassets/uiColors/Contents.json similarity index 100% rename from Resources/Assets.xcassets/uiColors/Contents.json rename to Resources/Tints.xcassets/uiColors/Contents.json diff --git a/Resources/Assets.xcassets/uiColors/bloo.colorset/Contents.json b/Resources/Tints.xcassets/uiColors/bloo.colorset/Contents.json similarity index 100% rename from Resources/Assets.xcassets/uiColors/bloo.colorset/Contents.json rename to Resources/Tints.xcassets/uiColors/bloo.colorset/Contents.json diff --git a/Resources/Assets.xcassets/uiColors/blue.colorset/Contents.json b/Resources/Tints.xcassets/uiColors/blue.colorset/Contents.json similarity index 100% rename from Resources/Assets.xcassets/uiColors/blue.colorset/Contents.json rename to Resources/Tints.xcassets/uiColors/blue.colorset/Contents.json diff --git a/Resources/Assets.xcassets/uiColors/green.colorset/Contents.json b/Resources/Tints.xcassets/uiColors/green.colorset/Contents.json similarity index 100% rename from Resources/Assets.xcassets/uiColors/green.colorset/Contents.json rename to Resources/Tints.xcassets/uiColors/green.colorset/Contents.json diff --git a/Resources/Assets.xcassets/uiColors/orange.colorset/Contents.json b/Resources/Tints.xcassets/uiColors/orange.colorset/Contents.json similarity index 100% rename from Resources/Assets.xcassets/uiColors/orange.colorset/Contents.json rename to Resources/Tints.xcassets/uiColors/orange.colorset/Contents.json diff --git a/Resources/Assets.xcassets/uiColors/pink.colorset/Contents.json b/Resources/Tints.xcassets/uiColors/pink.colorset/Contents.json similarity index 100% rename from Resources/Assets.xcassets/uiColors/pink.colorset/Contents.json rename to Resources/Tints.xcassets/uiColors/pink.colorset/Contents.json diff --git a/Resources/Assets.xcassets/uiColors/purple.colorset/Contents.json b/Resources/Tints.xcassets/uiColors/purple.colorset/Contents.json similarity index 100% rename from Resources/Assets.xcassets/uiColors/purple.colorset/Contents.json rename to Resources/Tints.xcassets/uiColors/purple.colorset/Contents.json diff --git a/Resources/Assets.xcassets/uiColors/red.colorset/Contents.json b/Resources/Tints.xcassets/uiColors/red.colorset/Contents.json similarity index 100% rename from Resources/Assets.xcassets/uiColors/red.colorset/Contents.json rename to Resources/Tints.xcassets/uiColors/red.colorset/Contents.json diff --git a/Resources/Assets.xcassets/uiColors/yellow.colorset/Contents.json b/Resources/Tints.xcassets/uiColors/yellow.colorset/Contents.json similarity index 100% rename from Resources/Assets.xcassets/uiColors/yellow.colorset/Contents.json rename to Resources/Tints.xcassets/uiColors/yellow.colorset/Contents.json diff --git a/Shared/CompleteEventButton.swift b/Shared/CompleteEventButton.swift new file mode 100644 index 0000000..985fc00 --- /dev/null +++ b/Shared/CompleteEventButton.swift @@ -0,0 +1,129 @@ +// +// CompleteEventButton.swift +// NearFuture +// +// Created by neon443 on 15/06/2025. +// + +import SwiftUI + +struct CompleteEventButton: View { + @ObservedObject var viewModel: EventViewModel + @Binding var event: Event + + @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.01, repeats: true) { timer in + guard completeInProgress else { return } + guard timer.isValid else { return } + let elapsed = Date().timeIntervalSince(completeStartTime) + progress = min(1, elapsed) + + if progress >= 1 { + withAnimation { completeInProgress = false } + viewModel.completeEvent(&event) +#if canImport(UIKit) + 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)) +} diff --git a/Shared/Extensions.swift b/Shared/Extensions.swift new file mode 100644 index 0000000..049e4c4 --- /dev/null +++ b/Shared/Extensions.swift @@ -0,0 +1,37 @@ +// +// Extension.swift +// NearFuture +// +// Created by neon443 on 21/05/2025. +// + +import Foundation +import SwiftUI + +extension View { + var backgroundGradient: some View { + return LinearGradient( + gradient: Gradient(colors: [.bgTop, .two]), + startPoint: .top, + endPoint: .bottom + ) + .ignoresSafeArea(.all) + } +} + +extension AnyTransition { + static var moveAndFade: AnyTransition { + .asymmetric( + insertion: .move(edge: .leading), + removal: .move(edge: .trailing) + ) + .combined(with: .opacity) + } + static var moveAndFadeReversed: AnyTransition { + .asymmetric( + insertion: .move(edge: .trailing), + removal: .move(edge: .leading) + ) + .combined(with: .opacity) + } +} diff --git a/Shared/Model/AccentIcon.swift b/Shared/Model/AccentIcon.swift new file mode 100644 index 0000000..6ec13c4 --- /dev/null +++ b/Shared/Model/AccentIcon.swift @@ -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 + } +} diff --git a/Shared/Model/ColorCodable.swift b/Shared/Model/ColorCodable.swift new file mode 100644 index 0000000..0f58bd5 --- /dev/null +++ b/Shared/Model/ColorCodable.swift @@ -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 + } + } +} diff --git a/NearFuture/Item.swift b/Shared/Model/Events.swift similarity index 76% rename from NearFuture/Item.swift rename to Shared/Model/Events.swift index 19b2d99..8b708fc 100644 --- a/NearFuture/Item.swift +++ b/Shared/Model/Events.swift @@ -11,6 +11,9 @@ import SwiftUI import WidgetKit import UserNotifications import AppIntents +#if canImport(AppKit) +import AppKit +#endif //@Model //final class Item { @@ -21,7 +24,7 @@ import AppIntents // } //} -struct Event: Identifiable, Codable, Equatable, Animatable { +struct Event: Identifiable, Codable, Equatable, Animatable, Hashable { var id = UUID() var name: String var complete: Bool @@ -37,52 +40,6 @@ struct Event: Identifiable, Codable, Equatable, Animatable { } } -struct ColorCodable: Codable, Equatable { - init(_ color: Color) { - let uiColor = UIColor(color) - var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 1.0 - uiColor.getRed(&r, green: &g, blue: &b, alpha: &a) - - self.red = Double(r) - self.green = Double(g) - self.blue = Double(b) - } - 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.red = Double(r) - self.green = Double(g) - self.blue = Double(b) - } - 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) { let calendar = Calendar.current let startOfDayNow = calendar.startOfDay(for: Date()) @@ -93,115 +50,19 @@ func daysUntilEvent(_ eventDate: Date) -> (long: String, short: String) { if days < 0 { //past return ( - "\(-days) day\(plu(days)) ago", + "\(-days)\nday\(plu(days)) ago", "\(days)d" ) } else { //future return ( - "\(days) day\(plu(days))", + "\(days)\nday\(plu(days))", "\(days)d" ) } } -struct Settings: Codable, Equatable { - var showCompletedInHome: Bool - var tint: ColorCodable - var showWhatsNew: Bool - var prevAppVersion: String -} - -struct AccentIcon { - var icon: UIImage - var color: Color - var name: String - init(_ colorName: String) { - if colorName == "orange" { - self.icon = UIImage(named: "AppIcon")! - } else { - self.icon = UIImage(named: colorName)! - } - self.color = Color(uiColor: UIColor(named: "uiColors/\(colorName)")!) - self.name = colorName - } -} - -class SettingsViewModel: ObservableObject { - @Published var settings: Settings = Settings( - showCompletedInHome: false, - tint: ColorCodable(uiColor: UIColor(named: "AccentColor")!), - showWhatsNew: true, - prevAppVersion: getVersion()+getBuildID() - ) - @Published var notifsGranted: Bool = false - - @Published var colorChoices: [AccentIcon] = [] - - let accentChoices: [String] = [ - "red", - "orange", - "yellow", - "green", - "blue", - "bloo", - "purple", - "pink" - ] - - @Published var device: (sf: String, label: String) - - init(load: Bool = true) { - self.device = getDevice() - if load { - loadSettings() - Task { - let requestResult = await requestNotifs() - await MainActor.run { - self.notifsGranted = requestResult - } - } - } - } - - func changeTint(to: String) { - if let uicolor = UIColor(named: "uiColors/\(to)") { - self.settings.tint = ColorCodable(uiColor: uicolor) - saveSettings() - } - } - - let appGroupSettingsStore = UserDefaults(suiteName: "group.NearFuture") ?? UserDefaults.standard - let icSettStore = NSUbiquitousKeyValueStore.default - - func loadSettings() { - let decoder = JSONDecoder() - if let icSettings = icSettStore.data(forKey: "settings") { - if let decodedSetts = try? decoder.decode(Settings.self, from: icSettings) { - self.settings = decodedSetts - } - } else if let savedData = appGroupSettingsStore.data(forKey: "settings") { - if let decodedSetts = try? decoder.decode(Settings.self, from: savedData) { - self.settings = decodedSetts - } - } - if self.settings.prevAppVersion != getVersion()+getBuildID() { - self.settings.showWhatsNew = true - } - } - - func saveSettings() { - let encoder = JSONEncoder() - if let encoded = try? encoder.encode(settings) { - appGroupSettingsStore.set(encoded, forKey: "settings") - icSettStore.set(encoded, forKey: "settings") - icSettStore.synchronize() - loadSettings() - } - } -} - -class EventViewModel: ObservableObject { +class EventViewModel: ObservableObject, @unchecked Sendable { @Published var events: [Event] = [] @Published var icloudData: [Event] = [] @@ -232,11 +93,31 @@ class EventViewModel: ObservableObject { @Published var localEventCount: Int = 0 @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) { self.editableTemplate = template if load { loadEvents() } +// AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) } //appgroup or regular userdefaults @@ -315,8 +196,8 @@ class EventViewModel: ObservableObject { updateSyncStatus() loadEvents() - Task { - await checkPendingNotifs(getNotifs()) + Task.detached { + await self.checkPendingNotifs(self.getNotifs()) } WidgetCenter.shared.reloadAllTimelines()//reload all widgets when saving events objectWillChange.send() @@ -341,6 +222,24 @@ class EventViewModel: ObservableObject { saveEvents() //sync with icloud } + func editEvent(_ editedEvent: Event) { + if let index = events.firstIndex(where: { editedEvent.id == $0.id }) { + self.events[index] = editedEvent + saveEvents() + } + } + + func completeEvent(_ event: inout Event) { + withAnimation { event.complete.toggle() } + let eventToModify = self.events.firstIndex() { currEvent in + currEvent.id == event.id + } + if let eventToModify = eventToModify { + self.events[eventToModify] = event + self.saveEvents() + } + } + func removeEvent(at index: IndexSet) { events.remove(atOffsets: index) saveEvents() //sync local and icl @@ -392,20 +291,32 @@ class EventViewModel: ObservableObject { return "" } - func importEvents(_ imported: String) throws { + func importEvents(_ imported: String, replace: Bool) throws { guard let data = Data(base64Encoded: imported) else { throw importError.invalidB64 } let decoder = JSONDecoder() do { let decoded = try decoder.decode([Event].self, from: data) - self.events = decoded + if replace { + self.events = decoded + } else { + self.events = self.events + decoded + } saveEvents() } catch { throw error } } + func updateiCStatus() { + hasUbiquitous = hasUbiquitousKeyValueStore() + lastSyncWasSuccessful = syncStatus.contains("Success") + lastSyncWasNormalAgo = lastSync?.timeIntervalSinceNow.isNormal ?? false + localCountEqualToiCloud = localEventCount == icloudEventCount + icloudCountEqualToLocal = icloudEventCount == localEventCount + } + //MARK: Danger Zone func dangerClearLocalData() { UserDefaults.standard.removeObject(forKey: "events") @@ -451,7 +362,7 @@ class EventViewModel: ObservableObject { } } -class dummyEventViewModel: EventViewModel { +class dummyEventViewModel: EventViewModel, @unchecked Sendable{ var template2: Event override init(load: Bool = false) { self.template2 = Event( @@ -580,6 +491,7 @@ func getBuildID() -> String { } func getDevice() -> (sf: String, label: String) { + #if canImport(UIKit) let asi = ProcessInfo().isiOSAppOnMac let model = UIDevice().model if asi { @@ -590,6 +502,10 @@ func getDevice() -> (sf: String, label: String) { return (sf: model.lowercased(), label: model) } return (sf: "iphone", label: "iPhone") + #elseif canImport(AppKit) + + return (sf: "desktopcomputer", label: "Mac") + #endif } extension Event: AppEntity { @@ -630,8 +546,7 @@ struct CompleteEvent: AppIntent { func perform() async throws -> some IntentResult { print("s") - var viewModel = EventViewModel() - var eventss = viewModel.events + let viewModel = EventViewModel() print("hip") guard let eventUUID = UUID(uuidString: eventID) else { print(":sdklfajk") diff --git a/Shared/Model/Settings.swift b/Shared/Model/Settings.swift new file mode 100644 index 0000000..a01d61d --- /dev/null +++ b/Shared/Model/Settings.swift @@ -0,0 +1,95 @@ +// +// Settings.swift +// MacNearFuture +// +// Created by neon443 on 21/05/2025. +// + +import Foundation +import SwiftUI +#if canImport(AppKit) +import AppKit +#endif + +struct NFSettings: Codable, Equatable { + var showCompletedInHome: Bool = false + var tint: ColorCodable = ColorCodable(.accentColor) + var showWhatsNew: Bool = true + var prevAppVersion: String = getVersion()+getBuildID() +} + +class SettingsViewModel: ObservableObject { + @Published var settings: NFSettings = NFSettings() + + @Published var notifsGranted: Bool = false + + @Published var colorChoices: [AccentIcon] = [] + + let accentChoices: [String] = [ + "red", + "orange", + "yellow", + "green", + "blue", + "bloo", + "purple", + "pink" + ] + + @Published var device: (sf: String, label: String) + + init(load: Bool = true) { + self.device = getDevice() + if load { + loadSettings() + Task.detached { + let requestResult = await requestNotifs() + await MainActor.run { + self.notifsGranted = requestResult + } + } + } + } + + func changeTint(to: String) { +#if canImport(UIKit) + if let uicolor = UIColor(named: "uiColors/\(to)") { + self.settings.tint = ColorCodable(uiColor: uicolor) + saveSettings() + } +#elseif canImport(AppKit) + if let nscolor = NSColor(named: "uiColors/\(to)") { + self.settings.tint = ColorCodable(nsColor: nscolor) + } +#endif + } + + let appGroupSettingsStore = UserDefaults(suiteName: "group.NearFuture") ?? UserDefaults.standard + let icSettStore = NSUbiquitousKeyValueStore.default + + func loadSettings() { + let decoder = JSONDecoder() + if let icSettings = icSettStore.data(forKey: "settings") { + if let decodedSetts = try? decoder.decode(NFSettings.self, from: icSettings) { + self.settings = decodedSetts + } + } else if let savedData = appGroupSettingsStore.data(forKey: "settings") { + if let decodedSetts = try? decoder.decode(NFSettings.self, from: savedData) { + self.settings = decodedSetts + } + } + if self.settings.prevAppVersion != getVersion()+getBuildID() { + self.settings.showWhatsNew = true + } + } + + func saveSettings() { + let encoder = JSONEncoder() + if let encoded = try? encoder.encode(settings) { + appGroupSettingsStore.set(encoded, forKey: "settings") + icSettStore.set(encoded, forKey: "settings") + icSettStore.synchronize() + loadSettings() + } + } +} diff --git a/Shared/Model/SymbolsPicker/SymbolsLoader.swift b/Shared/Model/SymbolsPicker/SymbolsLoader.swift new file mode 100644 index 0000000..abff58c --- /dev/null +++ b/Shared/Model/SymbolsPicker/SymbolsLoader.swift @@ -0,0 +1,36 @@ +// +// SymbolsLoader.swift +// NearFuture +// +// Created by neon443 on 14/06/2025. +// + +import Foundation + +class SymbolsLoader: ObservableObject { + @Published var allSymbols: [String] = [] + + init() { + self.allSymbols = getAllSymbols() + } + + func getSymbols(_ searched: String) -> [String] { + if searched.isEmpty { + return allSymbols + } 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 + } +} diff --git a/Shared/Model/SymbolsPicker/SymbolsPicker.swift b/Shared/Model/SymbolsPicker/SymbolsPicker.swift new file mode 100644 index 0000000..92f988d --- /dev/null +++ b/Shared/Model/SymbolsPicker/SymbolsPicker.swift @@ -0,0 +1,89 @@ +// +// 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 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("")) +} diff --git a/Shared/NearFutureApp.swift b/Shared/NearFutureApp.swift new file mode 100644 index 0000000..e01dbbb --- /dev/null +++ b/Shared/NearFutureApp.swift @@ -0,0 +1,23 @@ +// +// NearFutureApp.swift +// NearFuture +// +// Created by neon443 on 24/12/2024. +// + +import SwiftUI +import SwiftData + +@main +struct NearFutureApp: App { + @StateObject var settingsModel: SettingsViewModel = SettingsViewModel() + var body: some Scene { + WindowGroup { + ContentView( + viewModel: EventViewModel(), + settingsModel: settingsModel + ) + .tint(settingsModel.settings.tint.color) + } + } +} diff --git a/Shared/ViewModifiers.swift b/Shared/ViewModifiers.swift new file mode 100644 index 0000000..caa1ae9 --- /dev/null +++ b/Shared/ViewModifiers.swift @@ -0,0 +1,76 @@ +// +// ViewModifiers.swift +// NearFuture +// +// Created by neon443 on 13/06/2025. +// + +import Foundation +import SwiftUI + +struct hapticHeavy: ViewModifier { + var trigger: any Equatable + + init(trigger: any Equatable) { + self.trigger = trigger + } + + func body(content: Content) -> some View { + if #available(iOS 17, *) { + content + .sensoryFeedback(.impact(weight: .heavy, intensity: 1), trigger: trigger) + } else { + content + } + } +} + +struct glassButton: ViewModifier { + func body(content: Content) -> some View { + if #available(iOS 19, macOS 16, *) { + content.buttonStyle(.glass) + } else { + content.buttonStyle(.borderedProminent) + .clipShape(RoundedRectangle(cornerRadius: 15)) + .tint(.two) + } + } +} + +struct hapticSuccess: ViewModifier { + var trigger: any Equatable + + init(trigger: any Equatable) { + self.trigger = trigger + } + + func body(content: Content) -> some View { + if #available(iOS 17, *) { + content.sensoryFeedback(.success, trigger: trigger) + } else { + content + } + } +} + +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 + } + } +}